Skip to content

Commit 27ec18a

Browse files
committed
Add JSON and ProtoBuf helpers and improve docs
1 parent 8239e31 commit 27ec18a

File tree

12 files changed

+313
-114
lines changed

12 files changed

+313
-114
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ go get nhooyr.io/websocket
2323
- First class context.Context support
2424
- Thoroughly tested, fully passes the [autobahn-testsuite](https://github.com/crossbario/autobahn-testsuite)
2525
- Concurrent writes
26-
- Zero dependencies outside of the stdlib
26+
- Zero dependencies outside of the stdlib for the core library
27+
- JSON and ProtoBuf helpers in the wsjson and wspb subpackages
2728

2829
## Roadmap
2930

accept.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ type AcceptOptions struct {
3434
// The only time you need this is if your javascript is running on a different domain
3535
// than your WebSocket server.
3636
// Please think carefully about whether you really need this option before you use it.
37-
// If you do, remember if you store secure data in cookies, you wil need to verify the
38-
// Origin header.
37+
// If you do, remember that if you store secure data in cookies, you wil need to verify the
38+
// Origin header yourself otherwise you are exposing yourself to a CSRF attack.
3939
InsecureSkipVerify bool
4040
}
4141

ci/bench/entrypoint.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ go test --vet=off --run=^$ -bench=. \
99
-memprofile=profs/mem \
1010
-blockprofile=profs/block \
1111
-mutexprofile=profs/mutex \
12-
./...
12+
.
1313

1414
set +x
1515
echo

dial.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type DialOptions struct {
2323
HTTPClient *http.Client
2424

2525
// Header specifies the HTTP headers included in the handshake request.
26+
// TODO rename to HTTPHeader
2627
Header http.Header
2728

2829
// Subprotocols lists the subprotocols to negotiate with the server.

doc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// See https://tools.ietf.org/html/rfc6455
44
//
5-
// Please see https://nhooyr.io/websocket for thorough overview docs and a
5+
// Please see https://nhooyr.io/websocket for overview docs and a
66
// comparison with existing implementations.
77
//
88
// Conn, Dial, and Accept are the main entrypoints into this package. Use Dial to dial

example_echo_test.go

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ package websocket_test
22

33
import (
44
"context"
5-
"encoding/json"
65
"fmt"
76
"io"
8-
"io/ioutil"
97
"log"
108
"net"
119
"net/http"
@@ -15,6 +13,7 @@ import (
1513
"golang.org/x/xerrors"
1614

1715
"nhooyr.io/websocket"
16+
"nhooyr.io/websocket/wsjson"
1817
)
1918

2019
// Example_echo starts a WebSocket echo server and
@@ -58,11 +57,11 @@ func Example_echo() {
5857
}
5958

6059
// Output:
61-
// {"i":0}
62-
// {"i":1}
63-
// {"i":2}
64-
// {"i":3}
65-
// {"i":4}
60+
// 0
61+
// 1
62+
// 2
63+
// 3
64+
// 4
6665
}
6766

6867
// echoServer is the WebSocket echo server implementation.
@@ -142,39 +141,20 @@ func client(url string) error {
142141
defer c.Close(websocket.StatusInternalError, "")
143142

144143
for i := 0; i < 5; i++ {
145-
w, err := c.Writer(ctx, websocket.MessageText)
146-
if err != nil {
147-
return err
148-
}
149-
150-
e := json.NewEncoder(w)
151-
err = e.Encode(map[string]int{
144+
err = wsjson.Write(ctx, c, map[string]int{
152145
"i": i,
153146
})
154147
if err != nil {
155148
return err
156149
}
157150

158-
err = w.Close()
159-
if err != nil {
160-
return err
161-
}
162-
163-
typ, r, err := c.Reader(ctx)
164-
if err != nil {
165-
return err
166-
}
167-
168-
if typ != websocket.MessageText {
169-
return xerrors.Errorf("expected text message but got %v", typ)
170-
}
171-
172-
msg2, err := ioutil.ReadAll(r)
151+
v := map[string]int{}
152+
err = wsjson.Read(ctx, c, &v)
173153
if err != nil {
174154
return err
175155
}
176156

177-
fmt.Printf("%s", msg2)
157+
fmt.Printf("%v\n", v["i"])
178158
}
179159

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

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module nhooyr.io/websocket
33
go 1.12
44

55
require (
6+
github.com/golang/protobuf v1.3.1
67
github.com/google/go-cmp v0.2.0
78
github.com/kr/pretty v0.1.0 // indirect
89
go.coder.com/go-tools v0.0.0-20190317003359-0c6a35b74a16

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
2+
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
13
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
24
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
35
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=

websocket.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,9 +489,12 @@ func (w messageWriter) Close() error {
489489
// Reader will wait until there is a WebSocket data message to read from the connection.
490490
// It returns the type of the message and a reader to read it.
491491
// The passed context will also bound the reader.
492+
//
492493
// Your application must keep reading messages for the Conn to automatically respond to ping
493494
// and close frames and not become stuck waiting for a data message to be read.
494-
// Please ensure to read the full message from io.Reader.
495+
// Please ensure to read the full message from io.Reader. If you do not read till
496+
// io.EOF, the connection will break unless the next read would have yielded io.EOF.
497+
//
495498
// You can only read a single message at a time so do not call this method
496499
// concurrently.
497500
func (c *Conn) Reader(ctx context.Context) (MessageType, io.Reader, error) {

websocket_test.go

Lines changed: 138 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,21 @@ import (
1212
"net/url"
1313
"os"
1414
"os/exec"
15+
"reflect"
1516
"strconv"
1617
"strings"
1718
"sync/atomic"
1819
"testing"
1920
"time"
2021

22+
"github.com/golang/protobuf/ptypes"
23+
"github.com/golang/protobuf/ptypes/duration"
2124
"github.com/google/go-cmp/cmp"
2225
"golang.org/x/xerrors"
2326

2427
"nhooyr.io/websocket"
28+
"nhooyr.io/websocket/wsjson"
29+
"nhooyr.io/websocket/wspb"
2530
)
2631

2732
func TestHandshake(t *testing.T) {
@@ -201,84 +206,139 @@ func TestHandshake(t *testing.T) {
201206
return nil
202207
},
203208
},
204-
// {
205-
// name: "echo",
206-
// server: func(w http.ResponseWriter, r *http.Request) error {
207-
// c, err := websocket.Accept(w, r, websocket.AcceptOptions{})
208-
// if err != nil {
209-
// return err
210-
// }
211-
// defer c.Close(websocket.StatusInternalError, "")
212-
//
213-
// ctx, cancel := context.WithTimeout(r.Context(), time.Second*5)
214-
// defer cancel()
215-
//
216-
// write := func() error {
217-
// jc := websocket.JSONConn{
218-
// C: c,
219-
// }
220-
//
221-
// v := map[string]interface{}{
222-
// "anmol": "wowow",
223-
// }
224-
// err = jc.Write(ctx, v)
225-
// if err != nil {
226-
// return err
227-
// }
228-
// return nil
229-
// }
230-
// err = write()
231-
// if err != nil {
232-
// return err
233-
// }
234-
// err = write()
235-
// if err != nil {
236-
// return err
237-
// }
238-
//
239-
// c.Close(websocket.StatusNormalClosure, "")
240-
// return nil
241-
// },
242-
// client: func(ctx context.Context, u string) error {
243-
// c, _, err := websocket.Dial(ctx, u, websocket.DialOptions{})
244-
// if err != nil {
245-
// return err
246-
// }
247-
// defer c.Close(websocket.StatusInternalError, "")
248-
//
249-
// jc := websocket.JSONConn{
250-
// C: c,
251-
// }
252-
//
253-
// read := func() error {
254-
// var v interface{}
255-
// err = jc.Read(ctx, &v)
256-
// if err != nil {
257-
// return err
258-
// }
259-
//
260-
// exp := map[string]interface{}{
261-
// "anmol": "wowow",
262-
// }
263-
// if !reflect.DeepEqual(exp, v) {
264-
// return xerrors.Errorf("expected %v but got %v", exp, v)
265-
// }
266-
// return nil
267-
// }
268-
// err = read()
269-
// if err != nil {
270-
// return err
271-
// }
272-
// // Read twice to ensure the un EOFed previous reader works correctly.
273-
// err = read()
274-
// if err != nil {
275-
// return err
276-
// }
277-
//
278-
// c.Close(websocket.StatusNormalClosure, "")
279-
// return nil
280-
// },
281-
// },
209+
{
210+
name: "jsonEcho",
211+
server: func(w http.ResponseWriter, r *http.Request) error {
212+
c, err := websocket.Accept(w, r, websocket.AcceptOptions{})
213+
if err != nil {
214+
return err
215+
}
216+
defer c.Close(websocket.StatusInternalError, "")
217+
218+
ctx, cancel := context.WithTimeout(r.Context(), time.Second*5)
219+
defer cancel()
220+
221+
write := func() error {
222+
v := map[string]interface{}{
223+
"anmol": "wowow",
224+
}
225+
err := wsjson.Write(ctx, c, v)
226+
return err
227+
}
228+
err = write()
229+
if err != nil {
230+
return err
231+
}
232+
err = write()
233+
if err != nil {
234+
return err
235+
}
236+
237+
c.Close(websocket.StatusNormalClosure, "")
238+
return nil
239+
},
240+
client: func(ctx context.Context, u string) error {
241+
c, _, err := websocket.Dial(ctx, u, websocket.DialOptions{})
242+
if err != nil {
243+
return err
244+
}
245+
defer c.Close(websocket.StatusInternalError, "")
246+
247+
read := func() error {
248+
var v interface{}
249+
err := wsjson.Read(ctx, c, &v)
250+
if err != nil {
251+
return err
252+
}
253+
254+
exp := map[string]interface{}{
255+
"anmol": "wowow",
256+
}
257+
if !reflect.DeepEqual(exp, v) {
258+
return xerrors.Errorf("expected %v but got %v", exp, v)
259+
}
260+
return nil
261+
}
262+
err = read()
263+
if err != nil {
264+
return err
265+
}
266+
// Read twice to ensure the un EOFed previous reader works correctly.
267+
err = read()
268+
if err != nil {
269+
return err
270+
}
271+
272+
c.Close(websocket.StatusNormalClosure, "")
273+
return nil
274+
},
275+
},
276+
{
277+
name: "protobufEcho",
278+
server: func(w http.ResponseWriter, r *http.Request) error {
279+
c, err := websocket.Accept(w, r, websocket.AcceptOptions{})
280+
if err != nil {
281+
return err
282+
}
283+
defer c.Close(websocket.StatusInternalError, "")
284+
285+
ctx, cancel := context.WithTimeout(r.Context(), time.Second*5)
286+
defer cancel()
287+
288+
write := func() error {
289+
err := wspb.Write(ctx, c, ptypes.DurationProto(100))
290+
return err
291+
}
292+
err = write()
293+
if err != nil {
294+
return err
295+
}
296+
err = write()
297+
if err != nil {
298+
return err
299+
}
300+
301+
c.Close(websocket.StatusNormalClosure, "")
302+
return nil
303+
},
304+
client: func(ctx context.Context, u string) error {
305+
c, _, err := websocket.Dial(ctx, u, websocket.DialOptions{})
306+
if err != nil {
307+
return err
308+
}
309+
defer c.Close(websocket.StatusInternalError, "")
310+
311+
read := func() error {
312+
var v duration.Duration
313+
err := wspb.Read(ctx, c, &v)
314+
if err != nil {
315+
return err
316+
}
317+
318+
d, err := ptypes.Duration(&v)
319+
if err != nil {
320+
return xerrors.Errorf("failed to convert duration.Duration to time.Duration: %w", err)
321+
}
322+
const exp = time.Duration(100)
323+
if !reflect.DeepEqual(exp, d) {
324+
return xerrors.Errorf("expected %v but got %v", exp, d)
325+
}
326+
return nil
327+
}
328+
err = read()
329+
if err != nil {
330+
return err
331+
}
332+
// Read twice to ensure the un EOFed previous reader works correctly.
333+
err = read()
334+
if err != nil {
335+
return err
336+
}
337+
338+
c.Close(websocket.StatusNormalClosure, "")
339+
return nil
340+
},
341+
},
282342
{
283343
name: "cookies",
284344
server: func(w http.ResponseWriter, r *http.Request) error {

0 commit comments

Comments
 (0)