Files
IoT/ws-probe/main.go
T

118 lines
3.3 KiB
Go

// ws-probe — тестовый контейнер для проверки WebSocket (wss) через платформенный ingress Nubes.
//
// Назначение: подтвердить, что «Простой HTTP контейнер» платформы пропускает
// WebSocket upgrade (101 Switching Protocols) и держит долгоживущие соединения.
//
// Эндпоинты:
//
// GET / — инфо-страница (HTML)
// GET /health — {"status":"ok","version":...}
// GET /ws — WebSocket echo: отвечает на сообщения, шлёт ping каждые 20с
//
// Без кредов, без внешних зависимостей — только echo.
package main
import (
"encoding/json"
"log"
"net/http"
"os"
"time"
"github.com/gorilla/websocket"
)
var version = "dev"
const pageHTML = `<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>ws-probe</title></head>
<body style="font-family:system-ui,sans-serif;padding:2rem">
<h1>ws-probe</h1>
<p>WebSocket echo: <code>/ws</code></p>
<p>Health: <code>/health</code></p>
</body></html>`
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool { return true },
}
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
mux := http.NewServeMux()
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]string{"status": "ok", "version": version})
})
mux.HandleFunc("/ws", wsEcho)
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = w.Write([]byte(pageHTML))
})
log.Printf("ws-probe %s listening on :%s", version, port)
if err := http.ListenAndServe(":"+port, mux); err != nil {
log.Fatal(err)
}
}
// wsEcho — апгрейд до WebSocket, echo сообщений, ping каждые 20с.
func wsEcho(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("upgrade failed: %v", err)
return
}
defer conn.Close()
log.Printf("ws connected from %s (host=%s)", conn.RemoteAddr(), r.Host)
// Read deadline сбрасывается pong'ами — мёртвые соединения закрываются через 60с.
conn.SetReadDeadline(time.Now().Add(60 * time.Second))
conn.SetPongHandler(func(string) error {
conn.SetReadDeadline(time.Now().Add(60 * time.Second))
return nil
})
pingTicker := time.NewTicker(20 * time.Second)
defer pingTicker.Stop()
done := make(chan struct{})
go func() {
defer close(done)
for {
mt, msg, err := conn.ReadMessage()
if err != nil {
log.Printf("ws read error: %v", err)
return
}
log.Printf("ws recv %d bytes (type %d)", len(msg), mt)
if err := conn.WriteMessage(mt, msg); err != nil {
log.Printf("ws write error: %v", err)
return
}
log.Printf("ws echo sent")
}
}()
for {
select {
case <-done:
log.Printf("ws closed from %s", conn.RemoteAddr())
return
case t := <-pingTicker.C:
deadline := time.Now().Add(10 * time.Second)
if err := conn.WriteControl(websocket.PingMessage, []byte(t.Format(time.RFC3339)), deadline); err != nil {
log.Printf("ws ping error: %v", err)
return
}
}
}
}