Skip to content

Commit 60f2b1e

Browse files
committed
Significantly better examples and godoc
1 parent 9b9ebb7 commit 60f2b1e

File tree

6 files changed

+275
-336
lines changed

6 files changed

+275
-336
lines changed

README.md

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ go get nhooyr.io/websocket
2525
- context.Context is first class
2626
- net/http is used for WebSocket dials and upgrades
2727
- Thoroughly tested, fully passes the [autobahn-testsuite](https://github.com/crossbario/autobahn-testsuite)
28-
- JSON helpers
28+
- All returned errors include detailed context
2929

3030
## Roadmap
3131

@@ -34,45 +34,37 @@ go get nhooyr.io/websocket
3434

3535
## Example
3636

37+
For a production quality example that shows off the full API, see the echo example on the godoc.
38+
3739
### Server
3840

3941
```go
40-
fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
41-
c, err := websocket.Accept(w, r)
42+
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
43+
c, err := websocket.Accept(w, r, websocket.AcceptOptions{})
4244
if err != nil {
43-
log.Printf("server handshake failed: %v", err)
44-
return
45+
// ...
4546
}
4647
defer c.Close(websocket.StatusInternalError, "")
4748

48-
jc := websocket.JSONConn{
49-
Conn: c,
50-
}
51-
5249
ctx, cancel := context.WithTimeout(r.Context(), time.Second*10)
5350
defer cancel()
5451

55-
v := map[string]interface{}{
56-
"my_field": "foo",
52+
_, r, err := c.Reader(ctx)
53+
if err != nil {
54+
// ...
5755
}
58-
err = jc.Write(ctx, v)
56+
57+
b, err := ioutil.ReadAll(r)
5958
if err != nil {
60-
log.Printf("failed to write json: %v", err)
61-
return
59+
// ...
6260
}
63-
64-
log.Printf("wrote %v", v)
65-
61+
62+
fmt.Printf("received %q\n", b)
63+
6664
c.Close(websocket.StatusNormalClosure, "")
6765
})
68-
err := http.ListenAndServe("localhost:8080", fn)
69-
if err != nil {
70-
log.Fatalf("failed to listen and serve: %v", err)
71-
}
7266
```
7367

74-
For a production quality example that shows off the low level API, see the [echo server example](https://github.com/nhooyr/websocket/blob/master/example_test.go#L15).
75-
7668
### Client
7769

7870
```go
@@ -85,17 +77,20 @@ if err != nil {
8577
}
8678
defer c.Close(websocket.StatusInternalError, "")
8779

88-
jc := websocket.JSONConn{
89-
Conn: c,
80+
ww, err := c.Writer(ctx)
81+
if err != nil {
82+
// ...
9083
}
9184

92-
var v interface{}
93-
err = jc.Read(ctx, &v)
85+
_, err = ww.Write([]byte("hi"))
9486
if err != nil {
95-
return err
87+
// ...
9688
}
9789

98-
log.Printf("received %v", v)
90+
err = ww.Close()
91+
if err != nil {
92+
// ...
93+
}
9994

10095
c.Close(websocket.StatusNormalClosure, "")
10196
```

bench_test.go

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func benchConn(b *testing.B, stream bool) {
2828
if stream {
2929
streamEchoLoop(r.Context(), c)
3030
} else {
31-
streamEchoLoop(r.Context(), c)
31+
bufferedEchoLoop(r.Context(), c)
3232
}
3333

3434
}))
@@ -46,11 +46,10 @@ func benchConn(b *testing.B, stream bool) {
4646
defer c.Close(websocket.StatusInternalError, "")
4747

4848
runN := func(n int) {
49+
msg := []byte(strings.Repeat("2", n))
50+
buf := make([]byte, len(msg))
4951
b.Run(strconv.Itoa(n), func(b *testing.B) {
50-
msg := []byte(strings.Repeat("2", n))
51-
buf := make([]byte, len(msg))
5252
b.SetBytes(int64(len(msg)))
53-
b.ResetTimer()
5453
for i := 0; i < b.N; i++ {
5554
if stream {
5655
w, err := c.Writer(ctx, websocket.MessageText)
@@ -83,18 +82,17 @@ func benchConn(b *testing.B, stream bool) {
8382
b.Fatal(err)
8483
}
8584
}
86-
b.StopTimer()
8785
})
8886
}
8987

90-
runN(32)
91-
runN(128)
92-
runN(512)
93-
runN(1024)
88+
// runN(32)
89+
// runN(128)
90+
// runN(512)
91+
// runN(1024)
9492
runN(4096)
9593
runN(16384)
96-
runN(65536)
97-
runN(131072)
94+
// runN(65536)
95+
// runN(131072)
9896

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

example_echo_test.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package websocket_test
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"io/ioutil"
9+
"log"
10+
"net"
11+
"net/http"
12+
"time"
13+
14+
"golang.org/x/time/rate"
15+
"golang.org/x/xerrors"
16+
17+
"nhooyr.io/websocket"
18+
)
19+
20+
func Example_echo() {
21+
l, err := net.Listen("tcp", "localhost:0")
22+
if err != nil {
23+
log.Fatalf("failed to listen: %v", err)
24+
return
25+
}
26+
defer l.Close()
27+
28+
s := &http.Server{
29+
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
30+
err := echoServer(w, r)
31+
if err != nil {
32+
log.Printf("echo server: %v", err)
33+
}
34+
}),
35+
ReadTimeout: time.Second * 15,
36+
WriteTimeout: time.Second * 15,
37+
}
38+
defer s.Close()
39+
40+
go func() {
41+
err := s.Serve(l)
42+
if err != http.ErrServerClosed {
43+
log.Fatalf("failed to listen and serve: %v", err)
44+
}
45+
}()
46+
47+
err = client("ws://" + l.Addr().String())
48+
if err != nil {
49+
log.Fatalf("client failed: %v", err)
50+
}
51+
52+
// Output:
53+
// {"i":0}
54+
// {"i":1}
55+
// {"i":2}
56+
// {"i":3}
57+
// {"i":4}
58+
}
59+
60+
func echoServer(w http.ResponseWriter, r *http.Request) error {
61+
c, err := websocket.Accept(w, r, websocket.AcceptOptions{
62+
Subprotocols: []string{"echo"},
63+
})
64+
if err != nil {
65+
return err
66+
}
67+
defer c.Close(websocket.StatusInternalError, "")
68+
69+
if c.Subprotocol() == "" {
70+
c.Close(websocket.StatusPolicyViolation, "cannot communicate with the default protocol")
71+
return xerrors.Errorf("client does not speak echo sub protocol")
72+
}
73+
74+
ctx := r.Context()
75+
l := rate.NewLimiter(rate.Every(time.Millisecond*100), 10)
76+
77+
for {
78+
err = echo(ctx, c, l)
79+
if err != nil {
80+
return xerrors.Errorf("failed to echo: %w", err)
81+
}
82+
}
83+
}
84+
85+
func echo(ctx context.Context, c *websocket.Conn, l *rate.Limiter) error {
86+
ctx, cancel := context.WithTimeout(ctx, time.Minute)
87+
defer cancel()
88+
89+
err := l.Wait(ctx)
90+
if err != nil {
91+
return err
92+
}
93+
94+
typ, r, err := c.Reader(ctx)
95+
if err != nil {
96+
return err
97+
}
98+
r = io.LimitReader(r, 32768)
99+
100+
w, err := c.Writer(ctx, typ)
101+
if err != nil {
102+
return err
103+
}
104+
105+
_, err = io.Copy(w, r)
106+
if err != nil {
107+
return err
108+
}
109+
110+
err = w.Close()
111+
return err
112+
}
113+
114+
func client(url string) error {
115+
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
116+
defer cancel()
117+
118+
c, _, err := websocket.Dial(ctx, url, websocket.DialOptions{
119+
Subprotocols: []string{"echo"},
120+
})
121+
if err != nil {
122+
return err
123+
}
124+
defer c.Close(websocket.StatusInternalError, "")
125+
126+
for i := 0; i < 5; i++ {
127+
w, err := c.Writer(ctx, websocket.MessageText)
128+
if err != nil {
129+
return err
130+
}
131+
132+
e := json.NewEncoder(w)
133+
err = e.Encode(map[string]int{
134+
"i": i,
135+
})
136+
if err != nil {
137+
return err
138+
}
139+
140+
err = w.Close()
141+
if err != nil {
142+
return err
143+
}
144+
145+
typ, r, err := c.Reader(ctx)
146+
if err != nil {
147+
return err
148+
}
149+
150+
if typ != websocket.MessageText {
151+
return xerrors.Errorf("expected text message but got %v", typ)
152+
}
153+
154+
msg2, err := ioutil.ReadAll(r)
155+
if err != nil {
156+
return err
157+
}
158+
159+
fmt.Printf("%s", msg2)
160+
}
161+
162+
c.Close(websocket.StatusNormalClosure, "")
163+
return nil
164+
}

0 commit comments

Comments
 (0)