105 lines
3.4 KiB
Go
105 lines
3.4 KiB
Go
// Бэкенд стенда hpnn_v2: показывает имя хоста, дату и время, а также реальный
|
|||
|
|
// адрес клиента. Последнее — ключевая проверка схемы DNAT-без-SNAT: если в
|
||
|
|
// поле client стоит адрес из клиентской подсети, а не адрес балансировщика,
|
||
|
|
// значит трансляция источника нигде не выполняется.
|
||
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"log"
|
||
|
|
"net"
|
||
|
|
"net/http"
|
||
|
|
"os"
|
||
|
|
"strconv"
|
||
|
|
"sync/atomic"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
var requests atomic.Uint64
|
||
|
|
|
||
|
|
func localAddr(r *http.Request) string {
|
||
|
|
if a, ok := r.Context().Value(http.LocalAddrContextKey).(net.Addr); ok {
|
||
|
|
return a.String()
|
||
|
|
}
|
||
|
|
return "unknown"
|
||
|
|
}
|
||
|
|
|
||
|
|
func main() {
|
||
|
|
listen := os.Getenv("LISTEN")
|
||
|
|
if listen == "" {
|
||
|
|
listen = ":8080"
|
||
|
|
}
|
||
|
|
host, err := os.Hostname()
|
||
|
|
if err != nil {
|
||
|
|
host = "unknown"
|
||
|
|
}
|
||
|
|
|
||
|
|
mux := http.NewServeMux()
|
||
|
|
|
||
|
|
// Одна строка на запрос: удобно читать вывод цикла из 20 curl.
|
||
|
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
n := requests.Add(1)
|
||
|
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||
|
|
fmt.Fprintf(w, "backend=%-6s time=%s client=%-21s served=%-21s req=%d\n",
|
||
|
|
host,
|
||
|
|
time.Now().Format("2006-01-02 15:04:05 MST"),
|
||
|
|
r.RemoteAddr,
|
||
|
|
localAddr(r),
|
||
|
|
n)
|
||
|
|
})
|
||
|
|
|
||
|
|
// Развёрнутый вид — для ручного осмотра в браузере.
|
||
|
|
mux.HandleFunc("/info", func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
n := requests.Add(1)
|
||
|
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||
|
|
fmt.Fprintf(w, "Имя хоста : %s\n", host)
|
||
|
|
fmt.Fprintf(w, "Дата и время : %s\n", time.Now().Format("2006-01-02 15:04:05 MST -07:00"))
|
||
|
|
fmt.Fprintf(w, "Адрес клиента : %s\n", r.RemoteAddr)
|
||
|
|
fmt.Fprintf(w, "Адрес назначения: %s\n", localAddr(r))
|
||
|
|
fmt.Fprintf(w, "Host-заголовок : %s\n", r.Host)
|
||
|
|
fmt.Fprintf(w, "Запрос № : %d\n", n)
|
||
|
|
})
|
||
|
|
|
||
|
|
// Цель health-проб балансировщика.
|
||
|
|
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||
|
|
fmt.Fprintln(w, "ok")
|
||
|
|
})
|
||
|
|
|
||
|
|
// Длинный ответ: тело отдаётся по строке в секунду. Нужен, чтобы держать
|
||
|
|
// открытую сессию во время смены состава пула и проверять, что уже
|
||
|
|
// установленное соединение не рвётся. /slow?seconds=20
|
||
|
|
mux.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
seconds := 10
|
||
|
|
if v := r.URL.Query().Get("seconds"); v != "" {
|
||
|
|
if n, err := strconv.Atoi(v); err == nil && n > 0 && n <= 120 {
|
||
|
|
seconds = n
|
||
|
|
}
|
||
|
|
}
|
||
|
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||
|
|
flusher, _ := w.(http.Flusher)
|
||
|
|
for i := 1; i <= seconds; i++ {
|
||
|
|
fmt.Fprintf(w, "backend=%s tick=%d/%d time=%s\n", host, i, seconds,
|
||
|
|
time.Now().Format("15:04:05"))
|
||
|
|
if flusher != nil {
|
||
|
|
flusher.Flush()
|
||
|
|
}
|
||
|
|
select {
|
||
|
|
case <-r.Context().Done():
|
||
|
|
return
|
||
|
|
case <-time.After(time.Second):
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
srv := &http.Server{
|
||
|
|
Addr: listen,
|
||
|
|
Handler: mux,
|
||
|
|
ReadHeaderTimeout: 5 * time.Second,
|
||
|
|
}
|
||
|
|
|
||
|
|
log.Printf("backend %s слушает %s", host, listen)
|
||
|
|
if err := srv.ListenAndServe(); err != nil {
|
||
|
|
log.Fatal(err)
|
||
|
|
}
|
||
|
|
}
|