Skip to content

Commit

Permalink
Merge pull request #74 from nhooyr/release
Browse files Browse the repository at this point in the history
Fixes for release
  • Loading branch information
nhooyr authored Apr 27, 2019
2 parents 751a0ed + 4aa8fd7 commit 15db58e
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 52 deletions.
21 changes: 10 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ If you have any feedback, please feel free to open an issue.
## Install

```bash
go get nhooyr.io/websocket
go get nhooyr.io/websocket@0.2.0
```

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

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

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

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

Another advantage of nhooyr/websocket is that it supports multiple concurrent writers out
of the box.
Another advantage of nhooyr/websocket is that it supports concurrent writers out of the box.

### x/net/websocket

Expand All @@ -138,8 +136,9 @@ and clarity.

This library is fantastic in terms of performance. The author put in significant
effort to ensure its speed and I have applied as many of its optimizations as
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.
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.

If you want a library that gives you absolute control over everything, this is the library,
but for most users, the API provided by nhooyr/websocket will fit better as it is just as
performant but much easier to use correctly and idiomatic.
but for most users, the API provided by nhooyr/websocket will fit better as it is nearly just
as performant but much easier to use correctly and idiomatic.
8 changes: 5 additions & 3 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
//
// See https://tools.ietf.org/html/rfc6455
//
// Please see https://nhooyr.io/websocket for overview docs and a
// comparison with existing implementations.
//
// Conn, Dial, and Accept are the main entrypoints into this package. Use Dial to dial
// a WebSocket server, Accept to accept a WebSocket client dial and then Conn to interact
// with the resulting WebSocket connections.
//
// The examples are the best way to understand how to correctly use the library.
//
// The wsjson and wspb subpackages contain helpers for JSON and ProtoBuf messages.
//
// Please see https://nhooyr.io/websocket for more overview docs and a
// comparison with existing implementations.
//
// Please be sure to use the https://golang.org/x/xerrors package when inspecting returned errors.
package websocket
4 changes: 2 additions & 2 deletions example_echo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import (
"nhooyr.io/websocket/wsjson"
)

// This example starts a WebSocket echo server and
// then dials the server and sends 5 different messages
// This example starts a WebSocket echo server,
// dials the server and then sends 5 different messages
// and prints out the server's responses.
func Example_echo() {
// First we listen on port 0, that means the OS will
Expand Down
9 changes: 4 additions & 5 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ func ExampleAccept() {
c.Close(websocket.StatusNormalClosure, "")
})

http.ListenAndServe("localhost:8080", fn)
err := http.ListenAndServe("localhost:8080", fn)
log.Fatal(err)
}

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

c, _, err := websocket.Dial(ctx, "ws://localhost:8080", websocket.DialOptions{})
if err != nil {
log.Println(err)
return
log.Fatal(err)
}
defer c.Close(websocket.StatusInternalError, "the sky is falling")

err = wsjson.Write(ctx, c, "hi")
if err != nil {
log.Println(err)
return
log.Fatal(err)
}

c.Close(websocket.StatusNormalClosure, "")
Expand Down
1 change: 1 addition & 0 deletions statuscode.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const (

// CloseError represents a WebSocket close frame.
// It is returned by Conn's methods when the Connection is closed with a WebSocket close frame.
// You will need to use https://golang.org/x/xerrors to check for this error.
type CloseError struct {
Code StatusCode
Reason string
Expand Down
55 changes: 24 additions & 31 deletions websocket.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ type Conn struct {
}

func (c *Conn) close(err error) {
err = xerrors.Errorf("websocket closed: %w", err)

c.closeOnce.Do(func() {
runtime.SetFinalizer(c, nil)

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

c.closeErr = cerr
c.closeErr = xerrors.Errorf("websocket closed: %w", cerr)

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

runtime.SetFinalizer(c, func(c *Conn) {
c.Close(StatusInternalError, "connection garbage collected")
c.close(xerrors.New("connection garbage collected"))
})

go c.writeLoop()
Expand Down Expand Up @@ -238,7 +236,7 @@ func (c *Conn) handleControl(h header) {
case opClose:
ce, err := parseClosePayload(b)
if err != nil {
c.close(xerrors.Errorf("read invalid close payload: %w", err))
c.close(xerrors.Errorf("received invalid close payload: %w", err))
return
}
if ce.Code == StatusNoStatusRcvd {
Expand Down Expand Up @@ -302,7 +300,7 @@ func (c *Conn) readLoop() {
}
}

func (c *Conn) dataReadLoop(h header) (err error) {
func (c *Conn) dataReadLoop(h header) error {
maskPos := 0
left := h.payloadLength
firstReadDone := false
Expand Down Expand Up @@ -355,7 +353,6 @@ func (c *Conn) writePong(p []byte) error {

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

if cerr != c.closeErr {
if !xerrors.Is(c.closeErr, cerr) {
return c.closeErr
}

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

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

func (c *Conn) reader(ctx context.Context) (MessageType, io.Reader, error) {
for !atomic.CompareAndSwapInt64(&c.activeReader, 0, 1) {
select {
case <-c.closed:
return 0, nil, c.closeErr
case c.readBytes <- nil:
select {
case <-ctx.Done():
return 0, nil, ctx.Err()
case _, ok := <-c.readDone:
if !ok {
return 0, nil, c.closeErr
}
if atomic.LoadInt64(&c.activeReader) == 1 {
return 0, nil, xerrors.New("previous message not fully read")
}
}
case <-ctx.Done():
return 0, nil, ctx.Err()
if !atomic.CompareAndSwapInt64(&c.activeReader, 0, 1) {
// If the next read yields io.EOF we are good to go.
r := messageReader{
ctx: ctx,
c: c,
}
_, err := r.Read(nil)
if err == nil {
return 0, nil, xerrors.New("previous message not fully read")
}
if !xerrors.Is(err, io.EOF) {
return 0, nil, xerrors.Errorf("failed to check if last message at io.EOF: %w", err)
}

atomic.StoreInt64(&c.activeReader, 1)
}

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

0 comments on commit 15db58e

Please sign in to comment.