Skip to content

Commit ece99d2

Browse files
committed
Add *Conn.CloseRead for WASM
1 parent 4918725 commit ece99d2

File tree

4 files changed

+44
-27
lines changed

4 files changed

+44
-27
lines changed

conn.go

-21
Original file line numberDiff line numberDiff line change
@@ -406,27 +406,6 @@ func (c *Conn) reader(ctx context.Context) (MessageType, io.Reader, error) {
406406
return MessageType(h.opcode), r, nil
407407
}
408408

409-
// CloseRead will start a goroutine to read from the connection until it is closed or a data message
410-
// is received. If a data message is received, the connection will be closed with StatusPolicyViolation.
411-
// Since CloseRead reads from the connection, it will respond to ping, pong and close frames.
412-
// After calling this method, you cannot read any data messages from the connection.
413-
// The returned context will be cancelled when the connection is closed.
414-
//
415-
// Use this when you do not want to read data messages from the connection anymore but will
416-
// want to write messages to it.
417-
func (c *Conn) CloseRead(ctx context.Context) context.Context {
418-
atomic.StoreInt64(&c.readClosed, 1)
419-
420-
ctx, cancel := context.WithCancel(ctx)
421-
go func() {
422-
defer cancel()
423-
// We use the unexported reader so that we don't get the read closed error.
424-
c.reader(ctx)
425-
c.Close(StatusPolicyViolation, "unexpected data message")
426-
}()
427-
return ctx
428-
}
429-
430409
// messageReader enables reading a data frame from the WebSocket connection.
431410
type messageReader struct {
432411
c *Conn

doc.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@
2525
//
2626
// See https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
2727
//
28-
// Thus the unsupported features when compiling to WASM are:
28+
// Thus the unsupported features (not compiled in) for WASM are:
2929
// - Accept and AcceptOptions
30-
// - Conn's Reader, Writer, SetReadLimit, Ping methods
31-
// - HTTPClient and HTTPHeader dial options
30+
// - Conn's Reader, Writer, SetReadLimit and Ping methods
31+
// - HTTPClient and HTTPHeader fields in DialOptions
3232
//
3333
// The *http.Response returned by Dial will always either be nil or &http.Response{} as
3434
// we do not have access to the handshake response in the browser.

netconn.go

+25
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io"
88
"math"
99
"net"
10+
"sync/atomic"
1011
"time"
1112
)
1213

@@ -159,3 +160,27 @@ func (c *netConn) SetReadDeadline(t time.Time) error {
159160
}
160161
return nil
161162
}
163+
164+
// CloseRead will start a goroutine to read from the connection until it is closed or a data message
165+
// is received. If a data message is received, the connection will be closed with StatusPolicyViolation.
166+
// Since CloseRead reads from the connection, it will respond to ping, pong and close frames.
167+
// After calling this method, you cannot read any data messages from the connection.
168+
// The returned context will be cancelled when the connection is closed.
169+
//
170+
// Use this when you do not want to read data messages from the connection anymore but will
171+
// want to write messages to it.
172+
func (c *Conn) CloseRead(ctx context.Context) context.Context {
173+
atomic.StoreInt64(&c.readClosed, 1)
174+
175+
ctx, cancel := context.WithCancel(ctx)
176+
go func() {
177+
defer cancel()
178+
// We use the unexported reader method so that we don't get the read closed error.
179+
c.reader(ctx)
180+
// Either the connection is already closed since there was a read error
181+
// or the context was cancelled or a message was read and we should close
182+
// the connection.
183+
c.Close(StatusPolicyViolation, "unexpected data message")
184+
}()
185+
return ctx
186+
}

websocket_js.go

+16-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"reflect"
1111
"runtime"
1212
"sync"
13+
"sync/atomic"
1314
"syscall/js"
1415

1516
"nhooyr.io/websocket/internal/wsjs"
@@ -19,9 +20,10 @@ import (
1920
type Conn struct {
2021
ws wsjs.WebSocket
2122

22-
closeOnce sync.Once
23-
closed chan struct{}
24-
closeErr error
23+
readClosed int64
24+
closeOnce sync.Once
25+
closed chan struct{}
26+
closeErr error
2527

2628
releaseOnClose func()
2729
releaseOnMessage func()
@@ -67,6 +69,10 @@ func (c *Conn) init() {
6769
// Read attempts to read a message from the connection.
6870
// The maximum time spent waiting is bounded by the context.
6971
func (c *Conn) Read(ctx context.Context) (MessageType, []byte, error) {
72+
if atomic.LoadInt64(&c.readClosed) == 1 {
73+
return 0, nil, fmt.Errorf("websocket connection read closed")
74+
}
75+
7076
typ, p, err := c.read(ctx)
7177
if err != nil {
7278
return 0, nil, fmt.Errorf("failed to read: %w", err)
@@ -78,6 +84,7 @@ func (c *Conn) read(ctx context.Context) (MessageType, []byte, error) {
7884
var me wsjs.MessageEvent
7985
select {
8086
case <-ctx.Done():
87+
c.Close(StatusPolicyViolation, "read timed out")
8188
return 0, nil, ctx.Err()
8289
case me = <-c.readch:
8390
case <-c.closed:
@@ -198,6 +205,7 @@ func dial(ctx context.Context, url string, opts *DialOptions) (*Conn, *http.Resp
198205

199206
select {
200207
case <-ctx.Done():
208+
c.Close(StatusPolicyViolation, "dial timed out")
201209
return nil, nil, ctx.Err()
202210
case <-opench:
203211
case <-c.closed:
@@ -215,3 +223,8 @@ func (c *netConn) netConnReader(ctx context.Context) (MessageType, io.Reader, er
215223
}
216224
return typ, bytes.NewReader(p), nil
217225
}
226+
227+
// Only implemented for use by *Conn.CloseRead in netconn.go
228+
func (c *Conn) reader(ctx context.Context) {
229+
c.read(ctx)
230+
}

0 commit comments

Comments
 (0)