Skip to content

Commit

Permalink
Merge pull request #94 from nhooyr/removeReadLoop
Browse files Browse the repository at this point in the history
Remove readLoop
  • Loading branch information
nhooyr authored Jun 10, 2019
2 parents d8e872c + 73d39e2 commit 71e5415
Show file tree
Hide file tree
Showing 11 changed files with 306 additions and 350 deletions.
19 changes: 6 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,24 +123,17 @@ it has to reinvent hooks for TLS and proxies and prevents support of HTTP/2.
Some more advantages of nhooyr/websocket are that it supports concurrent writes and
makes it very easy to close the connection with a status code and reason.

nhooyr/websocket also responds to pings, pongs and close frames in a separate goroutine so that
your application doesn't always need to read from the connection unless it expects a data message.
gorilla/websocket requires you to constantly read from the connection to respond to control frames
even if you don't expect the peer to send any messages.

The ping API is also much nicer. gorilla/websocket requires registering a pong handler on the Conn
which results in awkward control flow. With nhooyr/websocket you use the Ping method on the Conn
that sends a ping and also waits for the pong.

In terms of performance, the differences depend on your application code. nhooyr/websocket
reuses buffers efficiently out of the box if you use the wsjson and wspb subpackages whereas
gorilla/websocket does not. As mentioned above, nhooyr/websocket also supports concurrent
writers out of the box.
In terms of performance, the differences mostly depend on your application code. nhooyr/websocket
reuses message buffers out of the box if you use the wsjson and wspb subpackages.
As mentioned above, nhooyr/websocket also supports concurrent writers.

The only performance con to nhooyr/websocket is that uses two extra goroutines. One for
reading pings, pongs and close frames async to application code and another to support
context.Context cancellation. This costs 4 KB of memory which is cheap compared
to the benefits.
The only performance con to nhooyr/websocket is that uses one extra goroutine to support
cancellation with context.Context and the net/http client side body upgrade.
This costs 2 KB of memory which is cheap compared to simplicity benefits.

### x/net/websocket

Expand Down
6 changes: 1 addition & 5 deletions accept.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,6 @@ func verifyClientRequest(w http.ResponseWriter, r *http.Request) error {
// Accept will reject the handshake if the Origin domain is not the same as the Host unless
// the InsecureSkipVerify option is set. In other words, by default it does not allow
// cross origin requests.
//
// The returned connection will be bound by r.Context(). Use conn.Context() to change
// the bounding context.
func Accept(w http.ResponseWriter, r *http.Request, opts AcceptOptions) (*Conn, error) {
c, err := accept(w, r, opts)
if err != nil {
Expand All @@ -109,7 +106,7 @@ func accept(w http.ResponseWriter, r *http.Request, opts AcceptOptions) (*Conn,
hj, ok := w.(http.Hijacker)
if !ok {
err = xerrors.New("passed ResponseWriter does not implement http.Hijacker")
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented)
return nil, err
}

Expand Down Expand Up @@ -143,7 +140,6 @@ func accept(w http.ResponseWriter, r *http.Request, opts AcceptOptions) (*Conn,
closer: netConn,
}
c.init()
c.Context(r.Context())

return c, nil
}
Expand Down
42 changes: 42 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,45 @@ func ExampleDial() {

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

// This example shows how to correctly handle a WebSocket connection
// on which you will only write and do not expect to read data messages.
func Example_writeOnly() {
fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c, err := websocket.Accept(w, r, websocket.AcceptOptions{})
if err != nil {
log.Println(err)
return
}
defer c.Close(websocket.StatusInternalError, "the sky is falling")

ctx, cancel := context.WithTimeout(r.Context(), time.Minute*10)
defer cancel()

go func() {
defer cancel()
c.Reader(ctx)
c.Close(websocket.StatusPolicyViolation, "server doesn't accept data messages")
}()

t := time.NewTicker(time.Second * 30)
defer t.Stop()

for {
select {
case <-ctx.Done():
c.Close(websocket.StatusNormalClosure, "")
return
case <-t.C:
err = wsjson.Write(ctx, c, "hi")
if err != nil {
log.Println(err)
return
}
}
}
})

err := http.ListenAndServe("localhost:8080", fn)
log.Fatal(err)
}
27 changes: 22 additions & 5 deletions header.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,19 @@ type header struct {
maskKey [4]byte
}

func makeWriteHeaderBuf() []byte {
return make([]byte, maxHeaderSize)
}

// bytes returns the bytes of the header.
// See https://tools.ietf.org/html/rfc6455#section-5.2
func marshalHeader(h header) []byte {
b := make([]byte, 2, maxHeaderSize)
func writeHeader(b []byte, h header) []byte {
if b == nil {
b = makeWriteHeaderBuf()
}

b = b[:2]
b[0] = 0

if h.fin {
b[0] |= 1 << 7
Expand Down Expand Up @@ -75,12 +84,20 @@ func marshalHeader(h header) []byte {
return b
}

func makeReadHeaderBuf() []byte {
return make([]byte, maxHeaderSize-2)
}

// readHeader reads a header from the reader.
// See https://tools.ietf.org/html/rfc6455#section-5.2
func readHeader(r io.Reader) (header, error) {
// We read the first two bytes directly so that we know
func readHeader(b []byte, r io.Reader) (header, error) {
if b == nil {
b = makeReadHeaderBuf()
}

// We read the first two bytes first so that we know
// exactly how long the header is.
b := make([]byte, 2, maxHeaderSize-2)
b = b[:2]
_, err := io.ReadFull(r, b)
if err != nil {
return header{}, err
Expand Down
8 changes: 4 additions & 4 deletions header_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ func TestHeader(t *testing.T) {
t.Run("readNegativeLength", func(t *testing.T) {
t.Parallel()

b := marshalHeader(header{
b := writeHeader(nil, header{
payloadLength: 1<<16 + 1,
})

// Make length negative
b[2] |= 1 << 7

r := bytes.NewReader(b)
_, err := readHeader(r)
_, err := readHeader(nil, r)
if err == nil {
t.Fatalf("unexpected error value: %+v", err)
}
Expand Down Expand Up @@ -90,9 +90,9 @@ func TestHeader(t *testing.T) {
}

func testHeader(t *testing.T, h header) {
b := marshalHeader(h)
b := writeHeader(nil, h)
r := bytes.NewReader(b)
h2, err := readHeader(r)
h2, err := readHeader(nil, r)
if err != nil {
t.Logf("header: %#v", h)
t.Logf("bytes: %b", b)
Expand Down
1 change: 0 additions & 1 deletion internal/bpool/bpool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ func BenchmarkSyncPool(b *testing.B) {

p := sync.Pool{}

b.ResetTimer()
for i := 0; i < b.N; i++ {
buf := p.Get()
if buf == nil {
Expand Down
34 changes: 0 additions & 34 deletions limitedreader.go

This file was deleted.

Loading

0 comments on commit 71e5415

Please sign in to comment.