Skip to content

Commit 15db58e

Browse files
authored
Merge pull request #74 from nhooyr/release
Fixes for release
2 parents 751a0ed + 4aa8fd7 commit 15db58e

File tree

6 files changed

+46
-52
lines changed

6 files changed

+46
-52
lines changed

README.md

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ If you have any feedback, please feel free to open an issue.
1414
## Install
1515

1616
```bash
17-
go get nhooyr.io/websocket
17+
go get nhooyr.io/websocket@0.2.0
1818
```
1919

2020
## Features
@@ -85,9 +85,8 @@ c.Close(websocket.StatusNormalClosure, "")
8585
- Minimal API is easier to maintain and learn
8686
- Context based cancellation is more ergonomic and robust than setting deadlines
8787
- No ping support because TCP keep alives work fine for HTTP/1.1 and they do not make
88-
sense with HTTP/2 (see #1)
89-
- net.Conn is never exposed as WebSocket's over HTTP/2 will not have a net.Conn.
90-
- Structures are nicer than functional options, see [google/go-cloud#908](https://github.com/google/go-cloud/issues/908#issuecomment-445034143)
88+
sense with HTTP/2 (see [#1](https://github.com/nhooyr/websocket/issues/1))
89+
- net.Conn is never exposed as WebSocket over HTTP/2 will not have a net.Conn.
9190
- Using net/http's Client for dialing means we do not have to reinvent dialing hooks
9291
and configurations like other WebSocket libraries
9392

@@ -105,7 +104,7 @@ in production.
105104
https://github.com/gorilla/websocket
106105

107106
This package is the community standard but it is 6 years old and over time
108-
has accumulated cruft. There are many ways to do the same thing, usage is not clear
107+
has accumulated cruft. Using is not clear as there are many ways to do things
109108
and there are some rough edges. Just compare the godoc of
110109
[nhooyr/websocket](https://godoc.org/github.com/nhooyr/websocket) side by side with
111110
[gorilla/websocket](https://godoc.org/github.com/gorilla/websocket).
@@ -115,11 +114,10 @@ which makes it easy to use correctly.
115114

116115
Furthermore, nhooyr/websocket has support for newer Go idioms such as context.Context and
117116
also uses net/http's Client and ResponseWriter directly for WebSocket handshakes.
118-
gorilla/websocket writes its handshakes directly to a net.Conn which means
117+
gorilla/websocket writes its handshakes to the underlying net.Conn which means
119118
it has to reinvent hooks for TLS and proxying and prevents support of HTTP/2.
120119

121-
Another advantage of nhooyr/websocket is that it supports multiple concurrent writers out
122-
of the box.
120+
Another advantage of nhooyr/websocket is that it supports concurrent writers out of the box.
123121

124122
### x/net/websocket
125123

@@ -138,8 +136,9 @@ and clarity.
138136

139137
This library is fantastic in terms of performance. The author put in significant
140138
effort to ensure its speed and I have applied as many of its optimizations as
141-
I could into nhooyr/websocket. Definitely check out his fantastic [blog post](https://medium.freecodecamp.org/million-websockets-and-go-cc58418460bb) about performant WebSocket servers.
139+
I could into nhooyr/websocket. Definitely check out his fantastic [blog post](https://medium.freecodecamp.org/million-websockets-and-go-cc58418460bb)
140+
about performant WebSocket servers.
142141

143142
If you want a library that gives you absolute control over everything, this is the library,
144-
but for most users, the API provided by nhooyr/websocket will fit better as it is just as
145-
performant but much easier to use correctly and idiomatic.
143+
but for most users, the API provided by nhooyr/websocket will fit better as it is nearly just
144+
as performant but much easier to use correctly and idiomatic.

doc.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22
//
33
// See https://tools.ietf.org/html/rfc6455
44
//
5-
// Please see https://nhooyr.io/websocket for overview docs and a
6-
// comparison with existing implementations.
7-
//
85
// Conn, Dial, and Accept are the main entrypoints into this package. Use Dial to dial
96
// a WebSocket server, Accept to accept a WebSocket client dial and then Conn to interact
107
// with the resulting WebSocket connections.
118
//
129
// The examples are the best way to understand how to correctly use the library.
1310
//
1411
// The wsjson and wspb subpackages contain helpers for JSON and ProtoBuf messages.
12+
//
13+
// Please see https://nhooyr.io/websocket for more overview docs and a
14+
// comparison with existing implementations.
15+
//
16+
// Please be sure to use the https://golang.org/x/xerrors package when inspecting returned errors.
1517
package websocket

example_echo_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import (
1616
"nhooyr.io/websocket/wsjson"
1717
)
1818

19-
// This example starts a WebSocket echo server and
20-
// then dials the server and sends 5 different messages
19+
// This example starts a WebSocket echo server,
20+
// dials the server and then sends 5 different messages
2121
// and prints out the server's responses.
2222
func Example_echo() {
2323
// First we listen on port 0, that means the OS will

example_test.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ func ExampleAccept() {
3636
c.Close(websocket.StatusNormalClosure, "")
3737
})
3838

39-
http.ListenAndServe("localhost:8080", fn)
39+
err := http.ListenAndServe("localhost:8080", fn)
40+
log.Fatal(err)
4041
}
4142

4243
// This example dials a server, writes a single JSON message and then
@@ -47,15 +48,13 @@ func ExampleDial() {
4748

4849
c, _, err := websocket.Dial(ctx, "ws://localhost:8080", websocket.DialOptions{})
4950
if err != nil {
50-
log.Println(err)
51-
return
51+
log.Fatal(err)
5252
}
5353
defer c.Close(websocket.StatusInternalError, "the sky is falling")
5454

5555
err = wsjson.Write(ctx, c, "hi")
5656
if err != nil {
57-
log.Println(err)
58-
return
57+
log.Fatal(err)
5958
}
6059

6160
c.Close(websocket.StatusNormalClosure, "")

statuscode.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const (
4242

4343
// CloseError represents a WebSocket close frame.
4444
// It is returned by Conn's methods when the Connection is closed with a WebSocket close frame.
45+
// You will need to use https://golang.org/x/xerrors to check for this error.
4546
type CloseError struct {
4647
Code StatusCode
4748
Reason string

websocket.go

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ type Conn struct {
6161
}
6262

6363
func (c *Conn) close(err error) {
64-
err = xerrors.Errorf("websocket closed: %w", err)
65-
6664
c.closeOnce.Do(func() {
6765
runtime.SetFinalizer(c, nil)
6866

@@ -71,7 +69,7 @@ func (c *Conn) close(err error) {
7169
cerr = err
7270
}
7371

74-
c.closeErr = cerr
72+
c.closeErr = xerrors.Errorf("websocket closed: %w", cerr)
7573

7674
close(c.closed)
7775
})
@@ -98,7 +96,7 @@ func (c *Conn) init() {
9896
c.readDone = make(chan int)
9997

10098
runtime.SetFinalizer(c, func(c *Conn) {
101-
c.Close(StatusInternalError, "connection garbage collected")
99+
c.close(xerrors.New("connection garbage collected"))
102100
})
103101

104102
go c.writeLoop()
@@ -238,7 +236,7 @@ func (c *Conn) handleControl(h header) {
238236
case opClose:
239237
ce, err := parseClosePayload(b)
240238
if err != nil {
241-
c.close(xerrors.Errorf("read invalid close payload: %w", err))
239+
c.close(xerrors.Errorf("received invalid close payload: %w", err))
242240
return
243241
}
244242
if ce.Code == StatusNoStatusRcvd {
@@ -302,7 +300,7 @@ func (c *Conn) readLoop() {
302300
}
303301
}
304302

305-
func (c *Conn) dataReadLoop(h header) (err error) {
303+
func (c *Conn) dataReadLoop(h header) error {
306304
maskPos := 0
307305
left := h.payloadLength
308306
firstReadDone := false
@@ -355,7 +353,6 @@ func (c *Conn) writePong(p []byte) error {
355353

356354
// Close closes the WebSocket connection with the given status code and reason.
357355
// It will write a WebSocket close frame with a timeout of 5 seconds.
358-
// Concurrent calls to Close are ok.
359356
func (c *Conn) Close(code StatusCode, reason string) error {
360357
err := c.exportedClose(code, reason)
361358
if err != nil {
@@ -400,7 +397,7 @@ func (c *Conn) writeClose(p []byte, cerr CloseError) error {
400397
return err
401398
}
402399

403-
if cerr != c.closeErr {
400+
if !xerrors.Is(c.closeErr, cerr) {
404401
return c.closeErr
405402
}
406403

@@ -420,9 +417,8 @@ func (c *Conn) writeSingleFrame(ctx context.Context, opcode opcode, p []byte) er
420417
payload: p,
421418
}:
422419
case <-ctx.Done():
423-
err := xerrors.Errorf("control frame write timed out: %w", ctx.Err())
424-
c.close(err)
425-
return err
420+
c.close(xerrors.Errorf("control frame write timed out: %w", ctx.Err()))
421+
return ctx.Err()
426422
}
427423

428424
select {
@@ -487,7 +483,7 @@ func (w messageWriter) write(p []byte) (int, error) {
487483
select {
488484
case <-w.ctx.Done():
489485
w.c.close(xerrors.Errorf("data write timed out: %w", w.ctx.Err()))
490-
// Wait for writeLoop to complete so we know p is done.
486+
// Wait for writeLoop to complete so we know p is done with.
491487
<-w.c.writeDone
492488
return 0, w.ctx.Err()
493489
case _, ok := <-w.c.writeDone:
@@ -542,25 +538,21 @@ func (c *Conn) Reader(ctx context.Context) (MessageType, io.Reader, error) {
542538
}
543539

544540
func (c *Conn) reader(ctx context.Context) (MessageType, io.Reader, error) {
545-
for !atomic.CompareAndSwapInt64(&c.activeReader, 0, 1) {
546-
select {
547-
case <-c.closed:
548-
return 0, nil, c.closeErr
549-
case c.readBytes <- nil:
550-
select {
551-
case <-ctx.Done():
552-
return 0, nil, ctx.Err()
553-
case _, ok := <-c.readDone:
554-
if !ok {
555-
return 0, nil, c.closeErr
556-
}
557-
if atomic.LoadInt64(&c.activeReader) == 1 {
558-
return 0, nil, xerrors.New("previous message not fully read")
559-
}
560-
}
561-
case <-ctx.Done():
562-
return 0, nil, ctx.Err()
541+
if !atomic.CompareAndSwapInt64(&c.activeReader, 0, 1) {
542+
// If the next read yields io.EOF we are good to go.
543+
r := messageReader{
544+
ctx: ctx,
545+
c: c,
563546
}
547+
_, err := r.Read(nil)
548+
if err == nil {
549+
return 0, nil, xerrors.New("previous message not fully read")
550+
}
551+
if !xerrors.Is(err, io.EOF) {
552+
return 0, nil, xerrors.Errorf("failed to check if last message at io.EOF: %w", err)
553+
}
554+
555+
atomic.StoreInt64(&c.activeReader, 1)
564556
}
565557

566558
select {
@@ -586,7 +578,8 @@ type messageReader struct {
586578
func (r messageReader) Read(p []byte) (int, error) {
587579
n, err := r.read(p)
588580
if err != nil {
589-
// Have to return io.EOF directly for now, cannot wrap.
581+
// Have to return io.EOF directly for now, we cannot wrap as xerrors
582+
// isn't used in stdlib.
590583
if err == io.EOF {
591584
return n, io.EOF
592585
}

0 commit comments

Comments
 (0)