Skip to content

Commit

Permalink
Allow configuration of websocket ping pong and write wait seconds (#524)
Browse files Browse the repository at this point in the history
Resolves #521
  • Loading branch information
StevenWeathers committed Mar 27, 2024
1 parent 959a6c6 commit 63c40bd
Show file tree
Hide file tree
Showing 17 changed files with 201 additions and 82 deletions.
3 changes: 3 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
27 changes: 15 additions & 12 deletions internal/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
27 changes: 27 additions & 0 deletions internal/http/checkin/checkin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -22,13 +47,15 @@ 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),
userService thunderdome.UserDataSvc, authService thunderdome.AuthDataSvc,
checkinService thunderdome.CheckinDataSvc, teamService thunderdome.TeamDataSvc,
) *Service {
c := &Service{
config: config,
logger: logger,
validateSessionCookie: validateSessionCookie,
validateUserCookie: validateUserCookie,
Expand Down
26 changes: 9 additions & 17 deletions internal/http/checkin/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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

Expand All @@ -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))
}
Expand All @@ -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
})

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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" {
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions internal/http/checkin/hub.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type message struct {
}

type subscription struct {
config *Config
conn *connection
arena string
UserID string
Expand Down
24 changes: 20 additions & 4 deletions internal/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
24 changes: 8 additions & 16 deletions internal/http/poker/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down Expand Up @@ -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

Expand All @@ -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))
}
Expand All @@ -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
})

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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" {
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions internal/http/poker/hub.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type message struct {
}

type subscription struct {
config *Config
conn *connection
arena string
UserID string
Expand Down
28 changes: 27 additions & 1 deletion internal/http/poker/poker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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,
Expand Down

0 comments on commit 63c40bd

Please sign in to comment.