From 8747cd81f96da82721089c90f3d93ea5a00b40f8 Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Thu, 11 Jun 2026 20:09:42 +1200 Subject: [PATCH] Chore: Compress websocket messages once per broadcast to improve performance (#695) --- server/websockets/client.go | 6 +++--- server/websockets/hub.go | 8 +++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/server/websockets/client.go b/server/websockets/client.go index 1fca11f..a7ef0cf 100644 --- a/server/websockets/client.go +++ b/server/websockets/client.go @@ -47,7 +47,7 @@ type Client struct { conn *websocket.Conn // Buffered channel of outbound messages. - send chan []byte + send chan *websocket.PreparedMessage } // ReadPump is used here solely to monitor the connection, not to actually receive messages. @@ -90,7 +90,7 @@ func (c *Client) writePump() { return } - if err := c.conn.WriteMessage(websocket.TextMessage, message); err != nil { + if err := c.conn.WritePreparedMessage(message); err != nil { return } case <-ticker.C: @@ -124,7 +124,7 @@ func ServeWs(hub *Hub, w http.ResponseWriter, r *http.Request) { return } - client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)} + client := &Client{hub: hub, conn: conn, send: make(chan *websocket.PreparedMessage, 256)} client.hub.register <- client // Allow collection of memory referenced by the caller by doing all work in new goroutines. diff --git a/server/websockets/hub.go b/server/websockets/hub.go index 266ab5e..a3f8b49 100644 --- a/server/websockets/hub.go +++ b/server/websockets/hub.go @@ -6,6 +6,7 @@ import ( "sync/atomic" "github.com/axllent/mailpit/internal/logger" + "github.com/gorilla/websocket" ) // Hub maintains the set of active clients and broadcasts messages to the @@ -61,9 +62,14 @@ func (h *Hub) Run() { h.clientCount.Add(-1) } case message := <-h.Broadcast: + prepared, err := websocket.NewPreparedMessage(websocket.TextMessage, message) + if err != nil { + logger.Log().Errorf("[websocket] error preparing message: %s", err.Error()) + continue + } for client := range h.Clients { select { - case client.send <- message: + case client.send <- prepared: default: close(client.send) delete(h.Clients, client)