mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2026-05-06 22:12:23 +08:00
73 lines
2.2 KiB
Go
73 lines
2.2 KiB
Go
package analytic
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/0xJacky/Nginx-UI/internal/helper"
|
|
"github.com/gorilla/websocket"
|
|
"github.com/uozi-tech/cosy/logger"
|
|
)
|
|
|
|
// Analytic push handlers only write to the client — they never read. Without
|
|
// ping/pong and read deadlines, a silently half-closed TCP connection keeps
|
|
// the server looping and writing for hours until the OS finally surfaces the
|
|
// error, while the client sees no updates and never triggers auto-reconnect.
|
|
// These constants mirror the values used by api/cluster/websocket.go so both
|
|
// sides of the cluster share the same keepalive contract.
|
|
const (
|
|
wsWriteWait = 10 * time.Second
|
|
wsPongWait = 60 * time.Second
|
|
wsPingPeriod = (wsPongWait * 9) / 10
|
|
)
|
|
|
|
// startWSKeepalive arms a read deadline + pong handler on the connection and
|
|
// spawns two goroutines: a reader that drains control frames (so pongs reset
|
|
// the deadline) and a pinger that emits a ping on pingPeriod. When the peer
|
|
// stops responding, the read deadline fires, the reader returns, and done is
|
|
// closed. The handler's deferred ws.Close() ultimately releases the socket —
|
|
// this helper never calls Close() itself on the read path, so a caller must
|
|
// `defer ws.Close()` after upgrade.
|
|
//
|
|
// The returned done channel is closed once the reader exits (read error,
|
|
// read deadline expiry, or peer close). Callers should select on it to bail
|
|
// out of their write loop promptly instead of waiting for the next WriteJSON
|
|
// to fail.
|
|
func startWSKeepalive(ws *websocket.Conn) <-chan struct{} {
|
|
done := make(chan struct{})
|
|
|
|
_ = ws.SetReadDeadline(time.Now().Add(wsPongWait))
|
|
ws.SetPongHandler(func(string) error {
|
|
return ws.SetReadDeadline(time.Now().Add(wsPongWait))
|
|
})
|
|
|
|
go func() {
|
|
defer close(done)
|
|
for {
|
|
if _, _, err := ws.ReadMessage(); err != nil {
|
|
if helper.IsUnexpectedWebsocketError(err) {
|
|
logger.Error("WebSocket read error:", err)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
ticker := time.NewTicker(wsPingPeriod)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
case <-ticker.C:
|
|
if err := ws.WriteControl(websocket.PingMessage, nil, time.Now().Add(wsWriteWait)); err != nil {
|
|
_ = ws.Close()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
return done
|
|
}
|