From 5cb3e7eb57955ebc6f09fda3732b9430e76808b0 Mon Sep 17 00:00:00 2001 From: Martin Turoci Date: Tue, 6 Feb 2024 08:48:02 +0100 Subject: [PATCH] feat: Make reconnect timeout configurable and add some extra logging. --- client.go | 41 +++++++++++++++++++---------------- cmd/wave/main.go | 7 ++++++ conf.go | 2 ++ server.go | 2 +- socket.go | 10 ++++++--- website/docs/configuration.md | 3 ++- 6 files changed, 41 insertions(+), 24 deletions(-) diff --git a/client.go b/client.go index 8d5269d2bb..85c1120018 100644 --- a/client.go +++ b/client.go @@ -54,26 +54,29 @@ type BootMsg struct { // Client represent a websocket (UI) client. type Client struct { - id string // unique id - auth *Auth // auth provider, might be nil - addr string // remote IP:port, used for logging only - session *Session // end-user session - broker *Broker // broker - conn *websocket.Conn // connection - routes []string // watched routes - data chan []byte // send data - editable bool // allow editing? // TODO move to user; tie to role - baseURL string // URL prefix of the Wave server - header *http.Header // forwarded headers from the WS connection - appPath string // path of the app this client is connected to, doesn't change throughout WS lifetime - pingInterval time.Duration - isReconnect bool - cancel context.CancelFunc + id string // unique id + auth *Auth // auth provider, might be nil + addr string // remote IP:port, used for logging only + session *Session // end-user session + broker *Broker // broker + conn *websocket.Conn // connection + routes []string // watched routes + data chan []byte // send data + editable bool // allow editing? // TODO move to user; tie to role + baseURL string // URL prefix of the Wave server + header *http.Header // forwarded headers from the WS connection + appPath string // path of the app this client is connected to, doesn't change throughout WS lifetime + pingInterval time.Duration + isReconnect bool + cancel context.CancelFunc + reconnectTimeout time.Duration } -func newClient(addr string, auth *Auth, session *Session, broker *Broker, conn *websocket.Conn, editable bool, baseURL string, header *http.Header, pingInterval time.Duration, isReconnect bool) *Client { +// TODO: Refactor some of the params into a Config struct. +func newClient(addr string, auth *Auth, session *Session, broker *Broker, conn *websocket.Conn, editable bool, + baseURL string, header *http.Header, pingInterval time.Duration, isReconnect bool, reconnectTimeout time.Duration) *Client { id := uuid.New().String() - return &Client{id, auth, addr, session, broker, conn, nil, make(chan []byte, 256), editable, baseURL, header, "", pingInterval, isReconnect, nil} + return &Client{id, auth, addr, session, broker, conn, nil, make(chan []byte, 256), editable, baseURL, header, "", pingInterval, isReconnect, nil, reconnectTimeout} } func (c *Client) refreshToken() error { @@ -97,8 +100,8 @@ func (c *Client) listen() { c.cancel = cancel go func(ctx context.Context) { select { - // Send disconnect message only if client doesn't reconnect within 2s. - case <-time.After(2 * time.Second): + // Send disconnect message only if client doesn't reconnect within the specified timeframe. + case <-time.After(c.reconnectTimeout): app := c.broker.getApp(c.appPath) if app != nil { app.forward(c.id, c.session, disconnectMsg) diff --git a/cmd/wave/main.go b/cmd/wave/main.go index d05097cbdb..9b70b537c5 100644 --- a/cmd/wave/main.go +++ b/cmd/wave/main.go @@ -181,6 +181,7 @@ func main() { panic(err) } + // TODO: Handle this at the config parser level. if authConf.SessionExpiry, err = time.ParseDuration(conf.SessionExpiry); err != nil { panic(err) } @@ -189,6 +190,10 @@ func main() { panic(err) } + if serverConf.ReconnectTimeout, err = time.ParseDuration(conf.ReconnectTimeout); err != nil { + panic(err) + } + serverConf.WebDir, _ = filepath.Abs(conf.WebDir) serverConf.DataDir, _ = filepath.Abs(conf.DataDir) serverConf.Version = Version @@ -227,6 +232,8 @@ func main() { authConf.URLParameters = append(authConf.URLParameters, kv) } } + + // TODO: Handle this at the config parser level. if authConf.SessionExpiry, err = time.ParseDuration(conf.SessionExpiry); err != nil { panic(err) } diff --git a/conf.go b/conf.go index 1b9a5dd716..78f368c46a 100644 --- a/conf.go +++ b/conf.go @@ -52,6 +52,7 @@ type ServerConf struct { ForwardedHeaders map[string]bool KeepAppLive bool PingInterval time.Duration + ReconnectTimeout time.Duration } type AuthConf struct { @@ -112,4 +113,5 @@ type Conf struct { SkipLogin bool `cfg:"oidc-skip-login" env:"H2O_WAVE_OIDC_SKIP_LOGIN" cfgDefault:"false" cfgHelper:"do not display the login form during OIDC authorization"` KeepAppLive bool `cfg:"keep-app-live" env:"H2O_WAVE_KEEP_APP_LIVE" cfgDefault:"false" cfgHelper:"do not unregister unresponsive apps"` Conf string `cfg:"conf" env:"H2O_WAVE_CONF" cfgDefault:".env" cfgHelper:"path to configuration file"` + ReconnectTimeout string `cfg:"reconnect-timeout" env:"H2O_WAVE_RECONNECT_TIMEOUT" cfgDefault:"2s" cfgHelper:"Time to wait for reconnect before dropping the client"` } diff --git a/server.go b/server.go index d700c132e6..84aa683f66 100644 --- a/server.go +++ b/server.go @@ -107,7 +107,7 @@ func Run(conf ServerConf) { handle("_auth/refresh", newRefreshHandler(auth, conf.Keychain)) } - handle("_s/", newSocketServer(broker, auth, conf.Editable, conf.BaseURL, conf.ForwardedHeaders, conf.PingInterval)) // XXX terminate sockets when logged out + handle("_s/", newSocketServer(broker, auth, conf.Editable, conf.BaseURL, conf.ForwardedHeaders, conf.PingInterval, conf.ReconnectTimeout)) fileDir := filepath.Join(conf.DataDir, "f") handle("_f/", newFileServer(fileDir, conf.Keychain, auth, conf.BaseURL+"_f")) diff --git a/socket.go b/socket.go index bc5b9cd56f..c2668bd307 100644 --- a/socket.go +++ b/socket.go @@ -31,10 +31,11 @@ type SocketServer struct { baseURL string forwardedHeaders map[string]bool pingInterval time.Duration + reconnectTimeout time.Duration } -func newSocketServer(broker *Broker, auth *Auth, editable bool, baseURL string, forwardedHeaders map[string]bool, pingInterval time.Duration) *SocketServer { - return &SocketServer{broker, auth, editable, baseURL, forwardedHeaders, pingInterval} +func newSocketServer(broker *Broker, auth *Auth, editable bool, baseURL string, forwardedHeaders map[string]bool, pingInterval, reconnectTimeout time.Duration) *SocketServer { + return &SocketServer{broker, auth, editable, baseURL, forwardedHeaders, pingInterval, reconnectTimeout} } func (s *SocketServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -78,8 +79,11 @@ func (s *SocketServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { if client.cancel != nil { client.cancel() } + if s.broker.debug { + echo(Log{"t": "socket_reconnect", "client_id": clientID, "addr": getRemoteAddr(r)}) + } } else { - client = newClient(getRemoteAddr(r), s.auth, session, s.broker, conn, s.editable, s.baseURL, &header, s.pingInterval, false) + client = newClient(getRemoteAddr(r), s.auth, session, s.broker, conn, s.editable, s.baseURL, &header, s.pingInterval, false, s.reconnectTimeout) } if msg, err := json.Marshal(OpsD{I: client.id}); err == nil { diff --git a/website/docs/configuration.md b/website/docs/configuration.md index 16b668de99..780c4d5a5e 100644 --- a/website/docs/configuration.md +++ b/website/docs/configuration.md @@ -12,7 +12,7 @@ Wave allows starting Wave server in 2 ways: Wave can be configured via configuration (`.env`) file, environment variables or command line arguments with the following priority: `cmd arg > env var > config > default`. - + | ENV var or config (wave run or waved) | CLI args (waved) | Description | |----------------------------------------|---------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | H2O_WAVE_ACCESS_KEY_ID | -access-key-id string | default API access key ID (default "access_key_id") | @@ -58,6 +58,7 @@ Wave can be configured via configuration (`.env`) file, environment variables or | H2O_WAVE_WEB_DIR | -web-dir string | directory to serve web assets from (default "./www") | | H2O_WAVE_CONF | -conf string | path to a configuration file (default ".env") | | H2O_WAVE_PING_INTERVAL | -ping-interval string | how often should ping messages be sent (e.g. 60s or 1m or 0.1h) to keep the websocket connection alive (default "50s") | +| H2O_WAVE_RECONNECT_TIMEOUT | -reconnect-timeout string | Time to wait for reconnect before dropping the client (default "2s") | [^1]: `1`, `t`, `true` to enable; `0`, `f`, `false` to disable (case insensitive). [^2]: Use OS-specific path list separator to specify multiple arguments - `:` for Linux/OSX and `;` for Windows. For example, `H2O_WAVE_PUBLIC_DIR=/images/@./files/images:/downloads/@./files/downloads`.