From e96e02a00642fcc64ac10b2aa0eb8c7174bddff1 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Fri, 15 Aug 2025 12:01:15 +0000 Subject: [PATCH 1/3] refactor: replace fmt.Errorf with MessageTooBigError --- read.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/read.go b/read.go index aab9e141..672e8f36 100644 --- a/read.go +++ b/read.go @@ -17,6 +17,14 @@ import ( "github.com/coder/websocket/internal/util" ) +type MessageTooBigError struct { + Limit int64 +} + +func (e MessageTooBigError) Error() string { + return fmt.Sprintf("read limited at %v bytes", e.Limit) +} + // Reader reads from the connection until there is a WebSocket // data message to be read. It will handle ping, pong and close frames as appropriate. // @@ -520,7 +528,7 @@ func (lr *limitReader) Read(p []byte) (int, error) { } if lr.n == 0 { - err := fmt.Errorf("read limited at %v bytes", lr.limit.Load()) + err := MessageTooBigError{Limit: lr.limit.Load()} lr.c.writeError(StatusMessageTooBig, err) return 0, err } From c44f12f2ac7c6363176eef98f1c96fbb572bf9b7 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Fri, 15 Aug 2025 14:06:19 +0000 Subject: [PATCH 2/3] refactor: replace MessageTooBigError with ErrMessageTooBig --- read.go | 10 ++-------- ws_js.go | 4 +++- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/read.go b/read.go index 672e8f36..85e09893 100644 --- a/read.go +++ b/read.go @@ -17,13 +17,7 @@ import ( "github.com/coder/websocket/internal/util" ) -type MessageTooBigError struct { - Limit int64 -} - -func (e MessageTooBigError) Error() string { - return fmt.Sprintf("read limited at %v bytes", e.Limit) -} +var ErrMessageTooBig = errors.New("websocket: message too big") // Reader reads from the connection until there is a WebSocket // data message to be read. It will handle ping, pong and close frames as appropriate. @@ -528,7 +522,7 @@ func (lr *limitReader) Read(p []byte) (int, error) { } if lr.n == 0 { - err := MessageTooBigError{Limit: lr.limit.Load()} + err := fmt.Errorf("%w: %d bytes", ErrMessageTooBig, lr.limit.Load()) lr.c.writeError(StatusMessageTooBig, err) return 0, err } diff --git a/ws_js.go b/ws_js.go index 8d52aeab..2381fe13 100644 --- a/ws_js.go +++ b/ws_js.go @@ -19,6 +19,8 @@ import ( "github.com/coder/websocket/internal/wsjs" ) +var ErrMessageTooBig = errors.New("websocket: message too big") + // opcode represents a WebSocket opcode. type opcode int @@ -144,7 +146,7 @@ func (c *Conn) Read(ctx context.Context) (MessageType, []byte, error) { } readLimit := c.msgReadLimit.Load() if readLimit >= 0 && int64(len(p)) > readLimit { - err := fmt.Errorf("read limited at %v bytes", c.msgReadLimit.Load()) + err := fmt.Errorf("%w: %d bytes", ErrMessageTooBig, c.msgReadLimit.Load()) c.Close(StatusMessageTooBig, err.Error()) return 0, nil, err } From bc076072c77beb587989b17f24ed3aeb13a35b6d Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Thu, 4 Sep 2025 15:19:14 +0300 Subject: [PATCH 3/3] add backwards compatibility and test --- conn_test.go | 19 +++++++++++++++++++ errors.go | 8 ++++++++ read.go | 11 +++++------ ws_js.go | 8 +++----- 4 files changed, 35 insertions(+), 11 deletions(-) create mode 100644 errors.go diff --git a/conn_test.go b/conn_test.go index 58ac394c..c3ccc886 100644 --- a/conn_test.go +++ b/conn_test.go @@ -421,6 +421,25 @@ func TestConn(t *testing.T) { err = c1.Close(websocket.StatusNormalClosure, "") assert.Success(t, err) }) + + t.Run("ReadLimitExceededReturnsErrMessageTooBig", func(t *testing.T) { + tt, c1, c2 := newConnTest(t, nil, nil) + + c1.SetReadLimit(1024) + _ = c2.CloseRead(tt.ctx) + + writeDone := xsync.Go(func() error { + payload := strings.Repeat("x", 4096) + return c2.Write(tt.ctx, websocket.MessageText, []byte(payload)) + }) + + _, _, err := c1.Read(tt.ctx) + assert.ErrorIs(t, websocket.ErrMessageTooBig, err) + assert.Contains(t, err, "read limited at 1025 bytes") + + _ = c2.CloseNow() + <-writeDone + }) } func TestWasm(t *testing.T) { diff --git a/errors.go b/errors.go new file mode 100644 index 00000000..bf4fc2b0 --- /dev/null +++ b/errors.go @@ -0,0 +1,8 @@ +package websocket + +import ( + "errors" +) + +// ErrMessageTooBig is returned when a message exceeds the read limit. +var ErrMessageTooBig = errors.New("websocket: message too big") diff --git a/read.go b/read.go index 85e09893..520acd50 100644 --- a/read.go +++ b/read.go @@ -17,8 +17,6 @@ import ( "github.com/coder/websocket/internal/util" ) -var ErrMessageTooBig = errors.New("websocket: message too big") - // Reader reads from the connection until there is a WebSocket // data message to be read. It will handle ping, pong and close frames as appropriate. // @@ -92,7 +90,8 @@ func (c *Conn) CloseRead(ctx context.Context) context.Context { // // By default, the connection has a message read limit of 32768 bytes. // -// When the limit is hit, the connection will be closed with StatusMessageTooBig. +// When the limit is hit, reads return an error wrapping ErrMessageTooBig and +// the connection is closed with StatusMessageTooBig. // // Set to -1 to disable. func (c *Conn) SetReadLimit(n int64) { @@ -522,9 +521,9 @@ func (lr *limitReader) Read(p []byte) (int, error) { } if lr.n == 0 { - err := fmt.Errorf("%w: %d bytes", ErrMessageTooBig, lr.limit.Load()) - lr.c.writeError(StatusMessageTooBig, err) - return 0, err + reason := fmt.Errorf("read limited at %d bytes", lr.limit.Load()) + lr.c.writeError(StatusMessageTooBig, reason) + return 0, fmt.Errorf("%w: %v", ErrMessageTooBig, reason) } if int64(len(p)) > lr.n { diff --git a/ws_js.go b/ws_js.go index 2381fe13..026b75fc 100644 --- a/ws_js.go +++ b/ws_js.go @@ -19,8 +19,6 @@ import ( "github.com/coder/websocket/internal/wsjs" ) -var ErrMessageTooBig = errors.New("websocket: message too big") - // opcode represents a WebSocket opcode. type opcode int @@ -146,9 +144,9 @@ func (c *Conn) Read(ctx context.Context) (MessageType, []byte, error) { } readLimit := c.msgReadLimit.Load() if readLimit >= 0 && int64(len(p)) > readLimit { - err := fmt.Errorf("%w: %d bytes", ErrMessageTooBig, c.msgReadLimit.Load()) - c.Close(StatusMessageTooBig, err.Error()) - return 0, nil, err + reason := fmt.Errorf("read limited at %d bytes", c.msgReadLimit.Load()) + c.Close(StatusMessageTooBig, reason.Error()) + return 0, nil, fmt.Errorf("%w: %v", ErrMessageTooBig, reason) } return typ, p, nil }