diff --git a/internal/config/config.go b/internal/config/config.go index 8005cd71..08b7cb99 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -29,6 +29,9 @@ func InitConfig(logger *otelzap.Logger) Config { viper.SetDefault("http.read_timeout", 5) viper.SetDefault("http.idle_timeout", 30) viper.SetDefault("http.read_header_timeout", 2) + viper.SetDefault("http.websocket_write_wait_sec", 10) + viper.SetDefault("http.websocket_ping_period_sec", 10) + viper.SetDefault("http.websocket_pong_wait_sec", 54) viper.SetDefault("analytics.enabled", true) viper.SetDefault("analytics.id", "UA-140245309-1") diff --git a/internal/config/types.go b/internal/config/types.go index 3db55082..65939570 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -14,18 +14,21 @@ type Config struct { } type Http struct { - CookieHashkey string `mapstructure:"cookie_hashkey"` - Port string - SecureCookie bool `mapstructure:"secure_cookie"` - BackendCookieName string `mapstructure:"backend_cookie_name"` - SessionCookieName string `mapstructure:"session_cookie_name"` - FrontendCookieName string `mapstructure:"frontend_cookie_name"` - Domain string - PathPrefix string `mapstructure:"path_prefix"` - WriteTimeout int `mapstructure:"write_timeout"` - ReadTimeout int `mapstructure:"read_timeout"` - IdleTimeout int `mapstructure:"idle_timeout"` - ReadHeaderTimeout int `mapstructure:"read_header_timeout"` + CookieHashkey string `mapstructure:"cookie_hashkey"` + Port string + SecureCookie bool `mapstructure:"secure_cookie"` + BackendCookieName string `mapstructure:"backend_cookie_name"` + SessionCookieName string `mapstructure:"session_cookie_name"` + FrontendCookieName string `mapstructure:"frontend_cookie_name"` + Domain string + PathPrefix string `mapstructure:"path_prefix"` + WriteTimeout int `mapstructure:"write_timeout"` + ReadTimeout int `mapstructure:"read_timeout"` + IdleTimeout int `mapstructure:"idle_timeout"` + ReadHeaderTimeout int `mapstructure:"read_header_timeout"` + WebsocketWriteWaitSec int `mapstructure:"websocket_write_wait_sec"` + WebsocketPingPeriodSec int `mapstructure:"websocket_ping_period_sec"` + WebsocketPongWaitSec int `mapstructure:"websocket_pong_wait_sec"` } type Analytics struct { diff --git a/internal/http/checkin/checkin.go b/internal/http/checkin/checkin.go index a6065b9d..8716c505 100644 --- a/internal/http/checkin/checkin.go +++ b/internal/http/checkin/checkin.go @@ -3,13 +3,38 @@ package checkin import ( "context" "net/http" + "time" "github.com/StevenWeathers/thunderdome-planning-poker/thunderdome" "github.com/uptrace/opentelemetry-go-extra/otelzap" ) +type Config struct { + // Time allowed to write a message to the peer. + WriteWaitSec int + + // Time allowed to read the next pong message from the peer. + PongWaitSec int + + // Send pings to peer with this period. Must be less than pongWait. + PingPeriodSec int +} + +func (c *Config) WriteWait() time.Duration { + return time.Duration(c.WriteWaitSec) * time.Second +} + +func (c *Config) PingPeriod() time.Duration { + return time.Duration(c.PingPeriodSec) * time.Second +} + +func (c *Config) PongWait() time.Duration { + return time.Duration(c.PongWaitSec) * time.Second +} + // Service provides retro service type Service struct { + config Config logger *otelzap.Logger validateSessionCookie func(w http.ResponseWriter, r *http.Request) (string, error) validateUserCookie func(w http.ResponseWriter, r *http.Request) (string, error) @@ -22,6 +47,7 @@ type Service struct { // New returns a new retro with websocket hub/client and event handlers func New( + config Config, logger *otelzap.Logger, validateSessionCookie func(w http.ResponseWriter, r *http.Request) (string, error), validateUserCookie func(w http.ResponseWriter, r *http.Request) (string, error), @@ -29,6 +55,7 @@ func New( checkinService thunderdome.CheckinDataSvc, teamService thunderdome.TeamDataSvc, ) *Service { c := &Service{ + config: config, logger: logger, validateSessionCookie: validateSessionCookie, validateUserCookie: validateUserCookie, diff --git a/internal/http/checkin/client.go b/internal/http/checkin/client.go index 52ad612a..de7c3d73 100644 --- a/internal/http/checkin/client.go +++ b/internal/http/checkin/client.go @@ -15,17 +15,8 @@ import ( ) const ( - // Time allowed to write a message to the peer. - writeWait = 10 * time.Second - - // Time allowed to read the next pong message from the peer. - pongWait = 60 * time.Second - - // Send pings to peer with this period. Must be less than pongWait. - pingPeriod = (pongWait * 9) / 10 - // Maximum message size allowed from peer. - maxMessageSize = 1024 * 1024 + maxMessageSize int64 = 1024 * 1024 ) var upgrader = websocket.Upgrader{ @@ -35,6 +26,7 @@ var upgrader = websocket.Upgrader{ // connection is a middleman between the websocket connection and the hub. type connection struct { + config *Config // The websocket connection. ws *websocket.Conn @@ -53,7 +45,7 @@ func (sub subscription) readPump(b *Service, ctx context.Context) { h.unregister <- sub if forceClosed { cm := websocket.FormatCloseMessage(4002, "abandoned") - if err := c.ws.WriteControl(websocket.CloseMessage, cm, time.Now().Add(writeWait)); err != nil { + if err := c.ws.WriteControl(websocket.CloseMessage, cm, time.Now().Add(sub.config.WriteWait())); err != nil { b.logger.Ctx(ctx).Error("abandon error", zap.Error(err), zap.String("team_id", TeamID), zap.String("session_user_id", UserID)) } @@ -64,9 +56,9 @@ func (sub subscription) readPump(b *Service, ctx context.Context) { } }() c.ws.SetReadLimit(maxMessageSize) - _ = c.ws.SetReadDeadline(time.Now().Add(pongWait)) + _ = c.ws.SetReadDeadline(time.Now().Add(sub.config.PongWait())) c.ws.SetPongHandler(func(string) error { - _ = c.ws.SetReadDeadline(time.Now().Add(pongWait)) + _ = c.ws.SetReadDeadline(time.Now().Add(sub.config.PongWait())) return nil }) @@ -121,14 +113,14 @@ func (sub subscription) readPump(b *Service, ctx context.Context) { // write a message with the given message type and payload. func (c *connection) write(mt int, payload []byte) error { - _ = c.ws.SetWriteDeadline(time.Now().Add(writeWait)) + _ = c.ws.SetWriteDeadline(time.Now().Add(c.config.WriteWait())) return c.ws.WriteMessage(mt, payload) } // writePump pumps messages from the hub to the websocket connection. func (sub *subscription) writePump() { c := sub.conn - ticker := time.NewTicker(pingPeriod) + ticker := time.NewTicker(sub.config.PingPeriod()) defer func() { ticker.Stop() _ = c.ws.Close() @@ -177,7 +169,7 @@ func (b *Service) ServeWs() http.HandlerFunc { zap.String("team_id", teamID)) return } - c := &connection{send: make(chan []byte, 256), ws: ws} + c := &connection{config: &b.config, send: make(chan []byte, 256), ws: ws} SessionId, cookieErr := b.validateSessionCookie(w, r) if cookieErr != nil && cookieErr.Error() != "NO_SESSION_COOKIE" { @@ -223,7 +215,7 @@ func (b *Service) ServeWs() http.HandlerFunc { return } - ss := subscription{c, teamID, User.Id} + ss := subscription{&b.config, c, teamID, User.Id} h.register <- ss initEvent := createSocketEvent("init", "", User.Id) diff --git a/internal/http/checkin/hub.go b/internal/http/checkin/hub.go index 93730059..af5becf6 100644 --- a/internal/http/checkin/hub.go +++ b/internal/http/checkin/hub.go @@ -6,6 +6,7 @@ type message struct { } type subscription struct { + config *Config conn *connection arena string UserID string diff --git a/internal/http/http.go b/internal/http/http.go index 05e9a6b1..0809f6f3 100644 --- a/internal/http/http.go +++ b/internal/http/http.go @@ -47,10 +47,26 @@ func New(apiService Service, FSS fs.FS, HFS http.FileSystem) *Service { a.Router.Use(otelmux.Middleware("thunderdome")) - pokerSvc := poker.New(a.Logger, a.Cookie.ValidateSessionCookie, a.Cookie.ValidateUserCookie, a.UserDataSvc, a.AuthDataSvc, a.PokerDataSvc) - retroSvc := retro.New(a.Logger, a.Cookie.ValidateSessionCookie, a.Cookie.ValidateUserCookie, a.UserDataSvc, a.AuthDataSvc, a.RetroDataSvc, a.Email) - storyboardSvc := storyboard.New(a.Logger, a.Cookie.ValidateSessionCookie, a.Cookie.ValidateUserCookie, a.UserDataSvc, a.AuthDataSvc, a.StoryboardDataSvc) - checkinSvc := checkin.New(a.Logger, a.Cookie.ValidateSessionCookie, a.Cookie.ValidateUserCookie, a.UserDataSvc, a.AuthDataSvc, a.CheckinDataSvc, a.TeamDataSvc) + pokerSvc := poker.New(poker.Config{ + WriteWaitSec: a.Config.WebsocketConfig.WriteWaitSec, + PongWaitSec: a.Config.WebsocketConfig.PongWaitSec, + PingPeriodSec: a.Config.WebsocketConfig.PingPeriodSec, + }, a.Logger, a.Cookie.ValidateSessionCookie, a.Cookie.ValidateUserCookie, a.UserDataSvc, a.AuthDataSvc, a.PokerDataSvc) + retroSvc := retro.New(retro.Config{ + WriteWaitSec: a.Config.WebsocketConfig.WriteWaitSec, + PongWaitSec: a.Config.WebsocketConfig.PongWaitSec, + PingPeriodSec: a.Config.WebsocketConfig.PingPeriodSec, + }, a.Logger, a.Cookie.ValidateSessionCookie, a.Cookie.ValidateUserCookie, a.UserDataSvc, a.AuthDataSvc, a.RetroDataSvc, a.Email) + storyboardSvc := storyboard.New(storyboard.Config{ + WriteWaitSec: a.Config.WebsocketConfig.WriteWaitSec, + PongWaitSec: a.Config.WebsocketConfig.PongWaitSec, + PingPeriodSec: a.Config.WebsocketConfig.PingPeriodSec, + }, a.Logger, a.Cookie.ValidateSessionCookie, a.Cookie.ValidateUserCookie, a.UserDataSvc, a.AuthDataSvc, a.StoryboardDataSvc) + checkinSvc := checkin.New(checkin.Config{ + WriteWaitSec: a.Config.WebsocketConfig.WriteWaitSec, + PongWaitSec: a.Config.WebsocketConfig.PongWaitSec, + PingPeriodSec: a.Config.WebsocketConfig.PingPeriodSec, + }, a.Logger, a.Cookie.ValidateSessionCookie, a.Cookie.ValidateUserCookie, a.UserDataSvc, a.AuthDataSvc, a.CheckinDataSvc, a.TeamDataSvc) swaggerJsonPath := "/" + a.Config.PathPrefix + "swagger/doc.json" validate = validator.New() diff --git a/internal/http/poker/client.go b/internal/http/poker/client.go index a2ec3bff..e9648a3b 100644 --- a/internal/http/poker/client.go +++ b/internal/http/poker/client.go @@ -17,15 +17,6 @@ import ( ) const ( - // Time allowed to write a message to the peer. - writeWait = 10 * time.Second - - // Time allowed to read the next pong message from the peer. - pongWait = 60 * time.Second - - // Send pings to peer with this period. Must be less than pongWait. - pingPeriod = (pongWait * 9) / 10 - // Maximum message size allowed from peer. maxMessageSize = 1024 * 1024 ) @@ -53,6 +44,7 @@ var upgrader = websocket.Upgrader{ // connection is a middleman between the websocket connection and the hub. type connection struct { + config *Config // The websocket connection. ws *websocket.Conn @@ -78,7 +70,7 @@ func (sub subscription) readPump(b *Service, ctx context.Context) { h.unregister <- sub if forceClosed { cm := websocket.FormatCloseMessage(4002, "abandoned") - if err := c.ws.WriteControl(websocket.CloseMessage, cm, time.Now().Add(writeWait)); err != nil { + if err := c.ws.WriteControl(websocket.CloseMessage, cm, time.Now().Add(sub.config.WriteWait())); err != nil { b.logger.Ctx(ctx).Error("abandon error", zap.Error(err), zap.String("poker_id", BattleID), zap.String("session_user_id", UserID)) } @@ -89,9 +81,9 @@ func (sub subscription) readPump(b *Service, ctx context.Context) { } }() c.ws.SetReadLimit(maxMessageSize) - _ = c.ws.SetReadDeadline(time.Now().Add(pongWait)) + _ = c.ws.SetReadDeadline(time.Now().Add(sub.config.PongWait())) c.ws.SetPongHandler(func(string) error { - _ = c.ws.SetReadDeadline(time.Now().Add(pongWait)) + _ = c.ws.SetReadDeadline(time.Now().Add(sub.config.PongWait())) return nil }) @@ -154,14 +146,14 @@ func (sub subscription) readPump(b *Service, ctx context.Context) { // write a message with the given message type and payload. func (c *connection) write(mt int, payload []byte) error { - _ = c.ws.SetWriteDeadline(time.Now().Add(writeWait)) + _ = c.ws.SetWriteDeadline(time.Now().Add(c.config.WriteWait())) return c.ws.WriteMessage(mt, payload) } // writePump pumps messages from the hub to the websocket connection. func (sub *subscription) writePump() { c := sub.conn - ticker := time.NewTicker(pingPeriod) + ticker := time.NewTicker(sub.config.PingPeriod()) defer func() { ticker.Stop() _ = c.ws.Close() @@ -211,7 +203,7 @@ func (b *Service) ServeBattleWs() http.HandlerFunc { zap.String("poker_id", battleID)) return } - c := &connection{send: make(chan []byte, 256), ws: ws} + c := &connection{config: &b.config, send: make(chan []byte, 256), ws: ws} SessionId, cookieErr := b.validateSessionCookie(w, r) if cookieErr != nil && cookieErr.Error() != "NO_SESSION_COOKIE" { @@ -297,7 +289,7 @@ func (b *Service) ServeBattleWs() http.HandlerFunc { } if UserAuthed { - ss := subscription{c, battleID, User.Id} + ss := subscription{&b.config, c, battleID, User.Id} h.register <- ss Users, _ := b.BattleService.AddUser(ss.arena, User.Id) diff --git a/internal/http/poker/hub.go b/internal/http/poker/hub.go index dd69316e..666a39b0 100644 --- a/internal/http/poker/hub.go +++ b/internal/http/poker/hub.go @@ -6,6 +6,7 @@ type message struct { } type subscription struct { + config *Config conn *connection arena string UserID string diff --git a/internal/http/poker/poker.go b/internal/http/poker/poker.go index b84cfaf3..929f7be9 100644 --- a/internal/http/poker/poker.go +++ b/internal/http/poker/poker.go @@ -4,13 +4,38 @@ package poker import ( "context" "net/http" + "time" "github.com/StevenWeathers/thunderdome-planning-poker/thunderdome" "github.com/uptrace/opentelemetry-go-extra/otelzap" ) +type Config struct { + // Time allowed to write a message to the peer. + WriteWaitSec int + + // Time allowed to read the next pong message from the peer. + PongWaitSec int + + // Send pings to peer with this period. Must be less than pongWait. + PingPeriodSec int +} + +func (c *Config) WriteWait() time.Duration { + return time.Duration(c.WriteWaitSec) * time.Second +} + +func (c *Config) PingPeriod() time.Duration { + return time.Duration(c.PingPeriodSec) * time.Second +} + +func (c *Config) PongWait() time.Duration { + return time.Duration(c.PongWaitSec) * time.Second +} + // Service provides battle service type Service struct { + config Config logger *otelzap.Logger validateSessionCookie func(w http.ResponseWriter, r *http.Request) (string, error) validateUserCookie func(w http.ResponseWriter, r *http.Request) (string, error) @@ -22,13 +47,14 @@ type Service struct { // New returns a new battle with websocket hub/client and event handlers func New( - logger *otelzap.Logger, + config Config, logger *otelzap.Logger, validateSessionCookie func(w http.ResponseWriter, r *http.Request) (string, error), validateUserCookie func(w http.ResponseWriter, r *http.Request) (string, error), userService thunderdome.UserDataSvc, authService thunderdome.AuthDataSvc, battleService thunderdome.PokerDataSvc, ) *Service { b := &Service{ + config: config, logger: logger, validateSessionCookie: validateSessionCookie, validateUserCookie: validateUserCookie, diff --git a/internal/http/retro/client.go b/internal/http/retro/client.go index d81d07f7..bd682665 100644 --- a/internal/http/retro/client.go +++ b/internal/http/retro/client.go @@ -17,15 +17,6 @@ import ( ) const ( - // Time allowed to write a message to the peer. - writeWait = 10 * time.Second - - // Time allowed to read the next pong message from the peer. - pongWait = 60 * time.Second - - // Send pings to peer with this period. Must be less than pongWait. - pingPeriod = (pongWait * 9) / 10 - // Maximum message size allowed from peer. maxMessageSize = 1024 * 1024 ) @@ -46,6 +37,7 @@ var upgrader = websocket.Upgrader{ // connection is a middleman between the websocket connection and the hub. type connection struct { + config *Config // The websocket connection. ws *websocket.Conn @@ -71,7 +63,7 @@ func (sub subscription) readPump(b *Service, ctx context.Context) { h.unregister <- sub if forceClosed { cm := websocket.FormatCloseMessage(4002, "abandoned") - if err := c.ws.WriteControl(websocket.CloseMessage, cm, time.Now().Add(writeWait)); err != nil { + if err := c.ws.WriteControl(websocket.CloseMessage, cm, time.Now().Add(sub.config.WriteWait())); err != nil { b.logger.Ctx(ctx).Error("abandon error", zap.Error(err), zap.String("session_user_id", UserID), zap.String("retro_id", RetroID)) } @@ -82,9 +74,9 @@ func (sub subscription) readPump(b *Service, ctx context.Context) { } }() c.ws.SetReadLimit(maxMessageSize) - _ = c.ws.SetReadDeadline(time.Now().Add(pongWait)) + _ = c.ws.SetReadDeadline(time.Now().Add(sub.config.PongWait())) c.ws.SetPongHandler(func(string) error { - _ = c.ws.SetReadDeadline(time.Now().Add(pongWait)) + _ = c.ws.SetReadDeadline(time.Now().Add(sub.config.PongWait())) return nil }) @@ -147,14 +139,14 @@ func (sub subscription) readPump(b *Service, ctx context.Context) { // write a message with the given message type and payload. func (c *connection) write(mt int, payload []byte) error { - _ = c.ws.SetWriteDeadline(time.Now().Add(writeWait)) + _ = c.ws.SetWriteDeadline(time.Now().Add(c.config.WriteWait())) return c.ws.WriteMessage(mt, payload) } // writePump pumps messages from the hub to the websocket connection. func (sub *subscription) writePump() { c := sub.conn - ticker := time.NewTicker(pingPeriod) + ticker := time.NewTicker(sub.config.PingPeriod()) defer func() { ticker.Stop() _ = c.ws.Close() @@ -204,7 +196,7 @@ func (b *Service) ServeWs() http.HandlerFunc { zap.String("retro_id", retroID)) return } - c := &connection{send: make(chan []byte, 256), ws: ws} + c := &connection{config: &b.config, send: make(chan []byte, 256), ws: ws} SessionId, cookieErr := b.validateSessionCookie(w, r) if cookieErr != nil && cookieErr.Error() != "NO_SESSION_COOKIE" { @@ -290,7 +282,7 @@ func (b *Service) ServeWs() http.HandlerFunc { } if UserAuthed { - ss := subscription{c, retroID, User.Id} + ss := subscription{&b.config, c, retroID, User.Id} h.register <- ss Users, _ := b.RetroService.RetroAddUser(ss.arena, User.Id) diff --git a/internal/http/retro/hub.go b/internal/http/retro/hub.go index 52bec6ca..5bbe67eb 100644 --- a/internal/http/retro/hub.go +++ b/internal/http/retro/hub.go @@ -6,6 +6,7 @@ type message struct { } type subscription struct { + config *Config conn *connection arena string UserID string diff --git a/internal/http/retro/retro.go b/internal/http/retro/retro.go index 3a689b90..76128712 100644 --- a/internal/http/retro/retro.go +++ b/internal/http/retro/retro.go @@ -3,13 +3,38 @@ package retro import ( "context" "net/http" + "time" "github.com/StevenWeathers/thunderdome-planning-poker/thunderdome" "github.com/uptrace/opentelemetry-go-extra/otelzap" ) +type Config struct { + // Time allowed to write a message to the peer. + WriteWaitSec int + + // Time allowed to read the next pong message from the peer. + PongWaitSec int + + // Send pings to peer with this period. Must be less than pongWait. + PingPeriodSec int +} + +func (c *Config) WriteWait() time.Duration { + return time.Duration(c.WriteWaitSec) * time.Second +} + +func (c *Config) PingPeriod() time.Duration { + return time.Duration(c.PingPeriodSec) * time.Second +} + +func (c *Config) PongWait() time.Duration { + return time.Duration(c.PongWaitSec) * time.Second +} + // Service provides retro service type Service struct { + config Config logger *otelzap.Logger validateSessionCookie func(w http.ResponseWriter, r *http.Request) (string, error) validateUserCookie func(w http.ResponseWriter, r *http.Request) (string, error) @@ -22,6 +47,7 @@ type Service struct { // New returns a new retro with websocket hub/client and event handlers func New( + config Config, logger *otelzap.Logger, validateSessionCookie func(w http.ResponseWriter, r *http.Request) (string, error), validateUserCookie func(w http.ResponseWriter, r *http.Request) (string, error), @@ -29,6 +55,7 @@ func New( retroService thunderdome.RetroDataSvc, emailService thunderdome.EmailService, ) *Service { rs := &Service{ + config: config, logger: logger, validateSessionCookie: validateSessionCookie, validateUserCookie: validateUserCookie, diff --git a/internal/http/storyboard/client.go b/internal/http/storyboard/client.go index 97f1c9eb..b29503a2 100644 --- a/internal/http/storyboard/client.go +++ b/internal/http/storyboard/client.go @@ -17,15 +17,6 @@ import ( ) const ( - // Time allowed to write a message to the peer. - writeWait = 10 * time.Second - - // Time allowed to read the next pong message from the peer. - pongWait = 60 * time.Second - - // Send pings to peer with this period. Must be less than pongWait. - pingPeriod = (pongWait * 9) / 10 - // Maximum message size allowed from peer. maxMessageSize = 1024 * 1024 ) @@ -45,6 +36,7 @@ var upgrader = websocket.Upgrader{ // connection is a middleman between the websocket connection and the hub. type connection struct { + config *Config // The websocket connection. ws *websocket.Conn @@ -70,7 +62,7 @@ func (sub subscription) readPump(b *Service, ctx context.Context) { h.unregister <- sub if forceClosed { cm := websocket.FormatCloseMessage(4002, "abandoned") - if err := c.ws.WriteControl(websocket.CloseMessage, cm, time.Now().Add(writeWait)); err != nil { + if err := c.ws.WriteControl(websocket.CloseMessage, cm, time.Now().Add(sub.config.WriteWait())); err != nil { b.Logger.Ctx(ctx).Error("abandon error", zap.Error(err), zap.String("session_user_id", UserID), zap.String("storyboard_id", StoryboardID)) } @@ -81,9 +73,9 @@ func (sub subscription) readPump(b *Service, ctx context.Context) { } }() c.ws.SetReadLimit(maxMessageSize) - _ = c.ws.SetReadDeadline(time.Now().Add(pongWait)) + _ = c.ws.SetReadDeadline(time.Now().Add(sub.config.PongWait())) c.ws.SetPongHandler(func(string) error { - _ = c.ws.SetReadDeadline(time.Now().Add(pongWait)) + _ = c.ws.SetReadDeadline(time.Now().Add(sub.config.PongWait())) return nil }) @@ -146,14 +138,14 @@ func (sub subscription) readPump(b *Service, ctx context.Context) { // write a message with the given message type and payload. func (c *connection) write(mt int, payload []byte) error { - _ = c.ws.SetWriteDeadline(time.Now().Add(writeWait)) + _ = c.ws.SetWriteDeadline(time.Now().Add(c.config.WriteWait())) return c.ws.WriteMessage(mt, payload) } // writePump pumps messages from the hub to the websocket connection. func (sub *subscription) writePump() { c := sub.conn - ticker := time.NewTicker(pingPeriod) + ticker := time.NewTicker(sub.config.PingPeriod()) defer func() { ticker.Stop() _ = c.ws.Close() @@ -203,7 +195,7 @@ func (b *Service) ServeWs() http.HandlerFunc { zap.String("storyboard_id", storyboardID)) return } - c := &connection{send: make(chan []byte, 256), ws: ws} + c := &connection{config: &b.config, send: make(chan []byte, 256), ws: ws} SessionId, cookieErr := b.ValidateSessionCookie(w, r) if cookieErr != nil && cookieErr.Error() != "NO_SESSION_COOKIE" { @@ -289,7 +281,7 @@ func (b *Service) ServeWs() http.HandlerFunc { } if UserAuthed { - ss := subscription{c, storyboardID, User.Id} + ss := subscription{&b.config, c, storyboardID, User.Id} h.register <- ss Users, _ := b.StoryboardService.AddUserToStoryboard(ss.arena, User.Id) diff --git a/internal/http/storyboard/hub.go b/internal/http/storyboard/hub.go index 6fe71f10..5ad692ee 100644 --- a/internal/http/storyboard/hub.go +++ b/internal/http/storyboard/hub.go @@ -6,6 +6,7 @@ type message struct { } type subscription struct { + config *Config conn *connection arena string UserID string diff --git a/internal/http/storyboard/storyboard.go b/internal/http/storyboard/storyboard.go index 70b5a3c7..7aaf9d4f 100644 --- a/internal/http/storyboard/storyboard.go +++ b/internal/http/storyboard/storyboard.go @@ -3,13 +3,38 @@ package storyboard import ( "context" "net/http" + "time" "github.com/StevenWeathers/thunderdome-planning-poker/thunderdome" "github.com/uptrace/opentelemetry-go-extra/otelzap" ) +type Config struct { + // Time allowed to write a message to the peer. + WriteWaitSec int + + // Time allowed to read the next pong message from the peer. + PongWaitSec int + + // Send pings to peer with this period. Must be less than pongWait. + PingPeriodSec int +} + +func (c *Config) WriteWait() time.Duration { + return time.Duration(c.WriteWaitSec) * time.Second +} + +func (c *Config) PingPeriod() time.Duration { + return time.Duration(c.PingPeriodSec) * time.Second +} + +func (c *Config) PongWait() time.Duration { + return time.Duration(c.PongWaitSec) * time.Second +} + // Service provides storyboard service type Service struct { + config Config Logger *otelzap.Logger ValidateSessionCookie func(w http.ResponseWriter, r *http.Request) (string, error) ValidateUserCookie func(w http.ResponseWriter, r *http.Request) (string, error) @@ -21,6 +46,7 @@ type Service struct { // New returns a new storyboard with websocket hub/client and event handlers func New( + config Config, logger *otelzap.Logger, validateSessionCookie func(w http.ResponseWriter, r *http.Request) (string, error), validateUserCookie func(w http.ResponseWriter, r *http.Request) (string, error), @@ -28,6 +54,7 @@ func New( storyboardService thunderdome.StoryboardDataSvc, ) *Service { sb := &Service{ + config: config, Logger: logger, ValidateSessionCookie: validateSessionCookie, ValidateUserCookie: validateUserCookie, diff --git a/internal/http/types.go b/internal/http/types.go index ccb00394..f490493d 100644 --- a/internal/http/types.go +++ b/internal/http/types.go @@ -22,6 +22,17 @@ const ( var validate *validator.Validate +type WebsocketConfig struct { + // Time allowed to write a message to the peer. + WriteWaitSec int + + // Time allowed to read the next pong message from the peer. + PongWaitSec int + + // Send pings to peer with this period. Must be less than pongWait. + PingPeriodSec int +} + // Config contains configuration values used by the APIs type Config struct { Port string @@ -74,6 +85,8 @@ type Config struct { AllowRegistration bool ShowActiveCountries bool SubscriptionsEnabled bool + + WebsocketConfig } type Service struct { diff --git a/main.go b/main.go index f5d29e10..c5b94b85 100644 --- a/main.go +++ b/main.go @@ -162,6 +162,11 @@ func main() { AllowRegistration: c.Config.AllowRegistration, ShowActiveCountries: c.Config.ShowActiveCountries, SubscriptionsEnabled: c.Config.SubscriptionsEnabled, + WebsocketConfig: http.WebsocketConfig{ + WriteWaitSec: c.Http.WebsocketWriteWaitSec, + PingPeriodSec: c.Http.WebsocketPingPeriodSec, + PongWaitSec: c.Http.WebsocketPongWaitSec, + }, }, Email: email.New(&email.Config{ AppURL: "https://" + c.Http.Domain + c.Http.PathPrefix + "/",