Skip to content

Commit 71e5415

Browse files
authored
Merge pull request #94 from nhooyr/removeReadLoop
Remove readLoop
2 parents d8e872c + 73d39e2 commit 71e5415

11 files changed

+306
-350
lines changed

README.md

+6-13
Original file line numberDiff line numberDiff line change
@@ -123,24 +123,17 @@ it has to reinvent hooks for TLS and proxies and prevents support of HTTP/2.
123123
Some more advantages of nhooyr/websocket are that it supports concurrent writes and
124124
makes it very easy to close the connection with a status code and reason.
125125

126-
nhooyr/websocket also responds to pings, pongs and close frames in a separate goroutine so that
127-
your application doesn't always need to read from the connection unless it expects a data message.
128-
gorilla/websocket requires you to constantly read from the connection to respond to control frames
129-
even if you don't expect the peer to send any messages.
130-
131126
The ping API is also much nicer. gorilla/websocket requires registering a pong handler on the Conn
132127
which results in awkward control flow. With nhooyr/websocket you use the Ping method on the Conn
133128
that sends a ping and also waits for the pong.
134129

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

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

145138
### x/net/websocket
146139

accept.go

+1-5
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,6 @@ func verifyClientRequest(w http.ResponseWriter, r *http.Request) error {
8181
// Accept will reject the handshake if the Origin domain is not the same as the Host unless
8282
// the InsecureSkipVerify option is set. In other words, by default it does not allow
8383
// cross origin requests.
84-
//
85-
// The returned connection will be bound by r.Context(). Use conn.Context() to change
86-
// the bounding context.
8784
func Accept(w http.ResponseWriter, r *http.Request, opts AcceptOptions) (*Conn, error) {
8885
c, err := accept(w, r, opts)
8986
if err != nil {
@@ -109,7 +106,7 @@ func accept(w http.ResponseWriter, r *http.Request, opts AcceptOptions) (*Conn,
109106
hj, ok := w.(http.Hijacker)
110107
if !ok {
111108
err = xerrors.New("passed ResponseWriter does not implement http.Hijacker")
112-
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
109+
http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented)
113110
return nil, err
114111
}
115112

@@ -143,7 +140,6 @@ func accept(w http.ResponseWriter, r *http.Request, opts AcceptOptions) (*Conn,
143140
closer: netConn,
144141
}
145142
c.init()
146-
c.Context(r.Context())
147143

148144
return c, nil
149145
}

example_test.go

+42
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,45 @@ func ExampleDial() {
5959

6060
c.Close(websocket.StatusNormalClosure, "")
6161
}
62+
63+
// This example shows how to correctly handle a WebSocket connection
64+
// on which you will only write and do not expect to read data messages.
65+
func Example_writeOnly() {
66+
fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
67+
c, err := websocket.Accept(w, r, websocket.AcceptOptions{})
68+
if err != nil {
69+
log.Println(err)
70+
return
71+
}
72+
defer c.Close(websocket.StatusInternalError, "the sky is falling")
73+
74+
ctx, cancel := context.WithTimeout(r.Context(), time.Minute*10)
75+
defer cancel()
76+
77+
go func() {
78+
defer cancel()
79+
c.Reader(ctx)
80+
c.Close(websocket.StatusPolicyViolation, "server doesn't accept data messages")
81+
}()
82+
83+
t := time.NewTicker(time.Second * 30)
84+
defer t.Stop()
85+
86+
for {
87+
select {
88+
case <-ctx.Done():
89+
c.Close(websocket.StatusNormalClosure, "")
90+
return
91+
case <-t.C:
92+
err = wsjson.Write(ctx, c, "hi")
93+
if err != nil {
94+
log.Println(err)
95+
return
96+
}
97+
}
98+
}
99+
})
100+
101+
err := http.ListenAndServe("localhost:8080", fn)
102+
log.Fatal(err)
103+
}

header.go

+22-5
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,19 @@ type header struct {
3131
maskKey [4]byte
3232
}
3333

34+
func makeWriteHeaderBuf() []byte {
35+
return make([]byte, maxHeaderSize)
36+
}
37+
3438
// bytes returns the bytes of the header.
3539
// See https://tools.ietf.org/html/rfc6455#section-5.2
36-
func marshalHeader(h header) []byte {
37-
b := make([]byte, 2, maxHeaderSize)
40+
func writeHeader(b []byte, h header) []byte {
41+
if b == nil {
42+
b = makeWriteHeaderBuf()
43+
}
44+
45+
b = b[:2]
46+
b[0] = 0
3847

3948
if h.fin {
4049
b[0] |= 1 << 7
@@ -75,12 +84,20 @@ func marshalHeader(h header) []byte {
7584
return b
7685
}
7786

87+
func makeReadHeaderBuf() []byte {
88+
return make([]byte, maxHeaderSize-2)
89+
}
90+
7891
// readHeader reads a header from the reader.
7992
// See https://tools.ietf.org/html/rfc6455#section-5.2
80-
func readHeader(r io.Reader) (header, error) {
81-
// We read the first two bytes directly so that we know
93+
func readHeader(b []byte, r io.Reader) (header, error) {
94+
if b == nil {
95+
b = makeReadHeaderBuf()
96+
}
97+
98+
// We read the first two bytes first so that we know
8299
// exactly how long the header is.
83-
b := make([]byte, 2, maxHeaderSize-2)
100+
b = b[:2]
84101
_, err := io.ReadFull(r, b)
85102
if err != nil {
86103
return header{}, err

header_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ func TestHeader(t *testing.T) {
2424
t.Run("readNegativeLength", func(t *testing.T) {
2525
t.Parallel()
2626

27-
b := marshalHeader(header{
27+
b := writeHeader(nil, header{
2828
payloadLength: 1<<16 + 1,
2929
})
3030

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

3434
r := bytes.NewReader(b)
35-
_, err := readHeader(r)
35+
_, err := readHeader(nil, r)
3636
if err == nil {
3737
t.Fatalf("unexpected error value: %+v", err)
3838
}
@@ -90,9 +90,9 @@ func TestHeader(t *testing.T) {
9090
}
9191

9292
func testHeader(t *testing.T, h header) {
93-
b := marshalHeader(h)
93+
b := writeHeader(nil, h)
9494
r := bytes.NewReader(b)
95-
h2, err := readHeader(r)
95+
h2, err := readHeader(nil, r)
9696
if err != nil {
9797
t.Logf("header: %#v", h)
9898
t.Logf("bytes: %b", b)

internal/bpool/bpool_test.go

-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ func BenchmarkSyncPool(b *testing.B) {
3232

3333
p := sync.Pool{}
3434

35-
b.ResetTimer()
3635
for i := 0; i < b.N; i++ {
3736
buf := p.Get()
3837
if buf == nil {

limitedreader.go

-34
This file was deleted.

0 commit comments

Comments
 (0)