Skip to content

Commit 4332546

Browse files
committed
Implement more of the API for WASM
Realized I can at least make the Reader/Writer/SetReadLimit methods work as expected even if they're not perfect.
1 parent 8c54bd9 commit 4332546

File tree

8 files changed

+72
-147
lines changed

8 files changed

+72
-147
lines changed

conn.go

+4-8
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ type Conn struct {
5959
msgReadLimit int64
6060

6161
// Used to ensure a previous writer is not used after being closed.
62-
activeWriter *messageWriter
62+
activeWriter atomic.Value
6363
// messageWriter state.
6464
writeMsgOpcode opcode
6565
writeMsgCtx context.Context
@@ -575,7 +575,7 @@ func (c *Conn) writer(ctx context.Context, typ MessageType) (io.WriteCloser, err
575575
w := &messageWriter{
576576
c: c,
577577
}
578-
c.activeWriter = w
578+
c.activeWriter.Store(w)
579579
return w, nil
580580
}
581581

@@ -607,7 +607,7 @@ type messageWriter struct {
607607
}
608608

609609
func (w *messageWriter) closed() bool {
610-
return w != w.c.activeWriter
610+
return w != w.c.activeWriter.Load()
611611
}
612612

613613
// Write writes the given bytes to the WebSocket connection.
@@ -645,7 +645,7 @@ func (w *messageWriter) close() error {
645645
if w.closed() {
646646
return fmt.Errorf("cannot use closed writer")
647647
}
648-
w.c.activeWriter = nil
648+
w.c.activeWriter.Store((*messageWriter)(nil))
649649

650650
_, err := w.c.writeFrame(w.c.writeMsgCtx, true, w.c.writeMsgOpcode, nil)
651651
if err != nil {
@@ -925,7 +925,3 @@ func (c *Conn) extractBufioWriterBuf(w io.Writer) {
925925

926926
c.bw.Reset(w)
927927
}
928-
929-
func (c *netConn) netConnReader(ctx context.Context) (MessageType, io.Reader, error) {
930-
return c.c.Reader(c.readContext)
931-
}

doc.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,20 @@
2626
// See https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
2727
//
2828
// Thus the unsupported features (not compiled in) for WASM are:
29+
//
2930
// - Accept and AcceptOptions
30-
// - Conn's Reader, Writer, SetReadLimit and Ping methods
31+
// - Conn.Ping
3132
// - HTTPClient and HTTPHeader fields in DialOptions
3233
//
3334
// The *http.Response returned by Dial will always either be nil or &http.Response{} as
3435
// we do not have access to the handshake response in the browser.
3536
//
37+
// The Writer method on the Conn buffers everything in memory and then sends it as a message
38+
// when the writer is closed.
39+
//
40+
// The Reader method also reads the entire response and then returns a reader that
41+
// reads from the byte slice.
42+
//
3643
// Writes are also always async so the passed context is no-op.
3744
//
3845
// Everything else is fully supported. This includes the wsjson and wspb helper packages.

netconn.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func (c *netConn) Read(p []byte) (int, error) {
9999
}
100100

101101
if c.reader == nil {
102-
typ, r, err := c.netConnReader(c.readContext)
102+
typ, r, err := c.c.Reader(c.readContext)
103103
if err != nil {
104104
var ce CloseError
105105
if errors.As(err, &ce) && (ce.Code == StatusNormalClosure) || (ce.Code == StatusGoingAway) {

websocket_js.go

+57-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"sync/atomic"
1414
"syscall/js"
1515

16+
"nhooyr.io/websocket/internal/bpool"
1617
"nhooyr.io/websocket/internal/wsjs"
1718
)
1819

@@ -106,6 +107,11 @@ func (c *Conn) read(ctx context.Context) (MessageType, []byte, error) {
106107
func (c *Conn) Write(ctx context.Context, typ MessageType, p []byte) error {
107108
err := c.write(ctx, typ, p)
108109
if err != nil {
110+
// Have to ensure the WebSocket is closed after a write error
111+
// to match the Go API. It can only error if the message type
112+
// is unexpected or the passed bytes contain invalid UTF-8 for
113+
// MessageText.
114+
c.Close(StatusInternalError, "something went wrong")
109115
return fmt.Errorf("failed to write: %w", err)
110116
}
111117
return nil
@@ -216,8 +222,10 @@ func dial(ctx context.Context, url string, opts *DialOptions) (*Conn, *http.Resp
216222
return c, &http.Response{}, nil
217223
}
218224

219-
func (c *netConn) netConnReader(ctx context.Context) (MessageType, io.Reader, error) {
220-
typ, p, err := c.c.Read(ctx)
225+
// Reader attempts to read a message from the connection.
226+
// The maximum time spent waiting is bounded by the context.
227+
func (c *Conn) Reader(ctx context.Context) (MessageType, io.Reader, error) {
228+
typ, p, err := c.Read(ctx)
221229
if err != nil {
222230
return 0, nil, err
223231
}
@@ -228,3 +236,50 @@ func (c *netConn) netConnReader(ctx context.Context) (MessageType, io.Reader, er
228236
func (c *Conn) reader(ctx context.Context) {
229237
c.read(ctx)
230238
}
239+
240+
// Writer returns a writer to write a WebSocket data message to the connection.
241+
// It buffers the entire message in memory and then sends it when the writer
242+
// is closed.
243+
func (c *Conn) Writer(ctx context.Context, typ MessageType) (io.WriteCloser, error) {
244+
return writer{
245+
c: c,
246+
ctx: ctx,
247+
typ: typ,
248+
b: bpool.Get(),
249+
}, nil
250+
}
251+
252+
type writer struct {
253+
closed bool
254+
255+
c *Conn
256+
ctx context.Context
257+
typ MessageType
258+
259+
b *bytes.Buffer
260+
}
261+
262+
func (w writer) Write(p []byte) (int, error) {
263+
if w.closed {
264+
return 0, errors.New("cannot write to closed writer")
265+
}
266+
n, err := w.b.Write(p)
267+
if err != nil {
268+
return n, fmt.Errorf("failed to write message: %w", err)
269+
}
270+
return n, nil
271+
}
272+
273+
func (w writer) Close() error {
274+
if w.closed {
275+
return errors.New("cannot close closed writer")
276+
}
277+
w.closed = true
278+
defer bpool.Put(w.b)
279+
280+
err := w.c.Write(w.ctx, w.typ, w.b.Bytes())
281+
if err != nil {
282+
return fmt.Errorf("failed to close writer: %w", err)
283+
}
284+
return nil
285+
}

wsjson/wsjson.go

+1-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// +build !js
2-
31
// Package wsjson provides websocket helpers for JSON messages.
42
package wsjson // import "nhooyr.io/websocket/wsjson"
53

@@ -34,9 +32,7 @@ func read(ctx context.Context, c *websocket.Conn, v interface{}) error {
3432
}
3533

3634
b := bpool.Get()
37-
defer func() {
38-
bpool.Put(b)
39-
}()
35+
defer bpool.Put(b)
4036

4137
_, err = b.ReadFrom(r)
4238
if err != nil {

wsjson/wsjson_js.go

-58
This file was deleted.

wspb/wspb.go

+1-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// +build !js
2-
31
// Package wspb provides websocket helpers for protobuf messages.
42
package wspb // import "nhooyr.io/websocket/wspb"
53

@@ -36,9 +34,7 @@ func read(ctx context.Context, c *websocket.Conn, v proto.Message) error {
3634
}
3735

3836
b := bpool.Get()
39-
defer func() {
40-
bpool.Put(b)
41-
}()
37+
defer bpool.Put(b)
4238

4339
_, err = b.ReadFrom(r)
4440
if err != nil {

wspb/wspb_js.go

-67
This file was deleted.

0 commit comments

Comments
 (0)