Skip to content

Commit 0746583

Browse files
committed
Rename DataType to MessageType
- Make messageReader a value type to avoid allocation - Add a bunch of important TODOs
1 parent 932d16d commit 0746583

File tree

8 files changed

+81
-55
lines changed

8 files changed

+81
-55
lines changed

accept.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func (o acceptOrigins) acceptOption() {}
4141
// Use this option with caution to avoid exposing your WebSocket
4242
// server to a CSRF attack.
4343
// See https://stackoverflow.com/a/37837709/4283659
44+
// TODO remove in favour of AcceptInsecureOrigin
4445
func AcceptOrigins(origins ...string) AcceptOption {
4546
return acceptOrigins(origins)
4647
}

datatype.go

Lines changed: 0 additions & 12 deletions
This file was deleted.

datatype_string.go

Lines changed: 0 additions & 25 deletions
This file was deleted.

example_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,18 @@ import (
1414

1515
func ExampleAccept_echo() {
1616
fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17-
c, err := websocket.Accept(w, r)
17+
c, err := websocket.Accept(w, r, websocket.AcceptSubprotocols("echo"))
1818
if err != nil {
1919
log.Printf("server handshake failed: %v", err)
2020
return
2121
}
2222
defer c.Close(websocket.StatusInternalError, "")
2323

24+
if c.Subprotocol() == "" {
25+
c.Close(websocket.StatusPolicyViolation, "cannot communicate with the default protocol")
26+
return
27+
}
28+
2429
echo := func() error {
2530
ctx, cancel := context.WithTimeout(r.Context(), time.Minute)
2631
defer cancel()

json.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func (jc *JSONConn) read(ctx context.Context, v interface{}) error {
2828
return err
2929
}
3030

31-
if typ != DataText {
31+
if typ != MessageText {
3232
return xerrors.Errorf("unexpected frame type for json (expected DataText): %v", typ)
3333
}
3434

@@ -39,6 +39,7 @@ func (jc *JSONConn) read(ctx context.Context, v interface{}) error {
3939
if err != nil {
4040
return xerrors.Errorf("failed to decode json: %w", err)
4141
}
42+
4243
return nil
4344
}
4445

@@ -52,7 +53,7 @@ func (jc JSONConn) Write(ctx context.Context, v interface{}) error {
5253
}
5354

5455
func (jc JSONConn) write(ctx context.Context, v interface{}) error {
55-
w := jc.Conn.Write(ctx, DataText)
56+
w := jc.Conn.Write(ctx, MessageText)
5657

5758
e := json.NewEncoder(w)
5859
err := e.Encode(v)

messagetype.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package websocket
2+
3+
// MessageType represents the Opcode of a WebSocket data frame.
4+
type MessageType int
5+
6+
//go:generate go run golang.org/x/tools/cmd/stringer -type=MessageType
7+
8+
// MessageType constants.
9+
const (
10+
MessageText MessageType = MessageType(opText)
11+
MessageBinary MessageType = MessageType(opBinary)
12+
)

messagetype_string.go

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

websocket.go

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ type control struct {
2323
type Conn struct {
2424
subprotocol string
2525
br *bufio.Reader
26-
// TODO switch to []byte for write buffering because for messages larger than buffers, there will always be 3 writes. One for the frame, one for the message, one for the fin.
27-
// Also will help for compression.
26+
// TODO switch to []byte for write buffering for predicting compression in memory maybe
2827
bw *bufio.Writer
2928
closer io.Closer
3029
client bool
@@ -36,7 +35,7 @@ type Conn struct {
3635
// Writers should send on write to begin sending
3736
// a message and then follow that up with some data
3837
// on writeBytes.
39-
write chan DataType
38+
write chan MessageType
4039
control chan control
4140
writeBytes chan []byte
4241
writeDone chan struct{}
@@ -45,9 +44,10 @@ type Conn struct {
4544
// Then send a byte slice to readBytes to read into it.
4645
// The n of bytes read will be sent on readDone once the read into a slice is complete.
4746
// readDone will receive 0 when EOF is reached.
48-
read chan opcode
49-
readBytes chan []byte
50-
readDone chan int
47+
read chan opcode
48+
readBytes chan []byte
49+
readDone chan int
50+
readerDone chan struct{}
5151
}
5252

5353
func (c *Conn) close(err error) {
@@ -77,12 +77,13 @@ func (c *Conn) Subprotocol() string {
7777

7878
func (c *Conn) init() {
7979
c.closed = make(chan struct{})
80-
c.write = make(chan DataType)
80+
c.write = make(chan MessageType)
8181
c.control = make(chan control)
8282
c.writeDone = make(chan struct{})
8383
c.read = make(chan opcode)
8484
c.readDone = make(chan int)
8585
c.readBytes = make(chan []byte)
86+
c.readerDone = make(chan struct{})
8687

8788
runtime.SetFinalizer(c, func(c *Conn) {
8889
c.Close(StatusInternalError, "websocket: connection ended up being garbage collected")
@@ -120,7 +121,7 @@ messageLoop:
120121
for {
121122
c.writeBytes = make(chan []byte)
122123

123-
var dataType DataType
124+
var dataType MessageType
124125
select {
125126
case <-c.closed:
126127
return
@@ -321,7 +322,7 @@ func (c *Conn) readLoop() {
321322
select {
322323
case <-c.closed:
323324
return
324-
case c.readDone <- 0:
325+
case c.readerDone <- struct{}{}:
325326
}
326327
}
327328
}
@@ -407,7 +408,8 @@ func (c *Conn) writeControl(ctx context.Context, opcode opcode, p []byte) error
407408
// a WebSocket data frame of type dataType to the connection.
408409
// Ensure you close the messageWriter once you have written to entire message.
409410
// Concurrent calls to messageWriter are ok.
410-
func (c *Conn) Write(ctx context.Context, dataType DataType) io.WriteCloser {
411+
func (c *Conn) Write(ctx context.Context, dataType MessageType) io.WriteCloser {
412+
// TODO acquire write here, move state into Conn and make messageWriter allocation free.
411413
return &messageWriter{
412414
c: c,
413415
ctx: ctx,
@@ -418,7 +420,7 @@ func (c *Conn) Write(ctx context.Context, dataType DataType) io.WriteCloser {
418420
// messageWriter enables writing to a WebSocket connection.
419421
// Ensure you close the messageWriter once you have written to entire message.
420422
type messageWriter struct {
421-
datatype DataType
423+
datatype MessageType
422424
ctx context.Context
423425
c *Conn
424426
acquiredLock bool
@@ -489,12 +491,16 @@ func (w *messageWriter) Close() error {
489491
// Please use SetContext on the reader to bound the read operation.
490492
// Your application must keep reading messages for the Conn to automatically respond to ping
491493
// and close frames.
492-
func (c *Conn) Read(ctx context.Context) (DataType, io.Reader, error) {
494+
func (c *Conn) Read(ctx context.Context) (MessageType, io.Reader, error) {
495+
// TODO error if the reader is not done
493496
select {
497+
case <-c.readerDone:
498+
// The previous reader just hit a io.EOF, we handle it for users
499+
return c.Read(ctx)
494500
case <-c.closed:
495501
return 0, nil, xerrors.Errorf("failed to read message: %w", c.closeErr)
496502
case opcode := <-c.read:
497-
return DataType(opcode), &messageReader{
503+
return MessageType(opcode), messageReader{
498504
ctx: ctx,
499505
c: c,
500506
}, nil
@@ -510,13 +516,26 @@ type messageReader struct {
510516
}
511517

512518
// Read reads as many bytes as possible into p.
513-
func (r *messageReader) Read(p []byte) (n int, err error) {
519+
func (r messageReader) Read(p []byte) (int, error) {
520+
n, err := r.read(p)
521+
if err != nil {
522+
// Have to return io.EOF directly for now.
523+
if err == io.EOF {
524+
return 0, io.EOF
525+
}
526+
return n, xerrors.Errorf("failed to read: %w", err)
527+
}
528+
return n, nil
529+
}
530+
531+
func (r messageReader) read(p []byte) (int, error) {
514532
select {
515533
case <-r.c.closed:
516534
return 0, r.c.closeErr
517-
case <-r.c.readDone:
535+
case <-r.c.readerDone:
518536
return 0, io.EOF
519537
case r.c.readBytes <- p:
538+
// TODO this is potentially racey as if we return if the context is cancelled, or the conn is closed we don't know if the p is ok to use. we must close the connection and also ensure the readLoop is done before returning, likewise with writes.
520539
select {
521540
case <-r.c.closed:
522541
return 0, r.c.closeErr

0 commit comments

Comments
 (0)