Skip to content

Commit 2b1f537

Browse files
committed
Improve README.md
Closes #30
1 parent f6ccff4 commit 2b1f537

File tree

9 files changed

+159
-54
lines changed

9 files changed

+159
-54
lines changed

README.md

Lines changed: 111 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,47 +4,139 @@
44
[![Codecov](https://img.shields.io/codecov/c/github/nhooyr/ws.svg)](https://codecov.io/gh/nhooyr/ws)
55
[![GitHub release](https://img.shields.io/github/release/nhooyr/ws.svg)](https://github.com/nhooyr/ws/releases)
66

7-
ws is a clean and idiomatic WebSocket library for Go.
7+
ws is a minimal and idiomatic WebSocket library for Go.
88

99
This library is in heavy development.
1010

1111
## Install
1212

1313
```bash
14-
go get nhooyr.io/ws
14+
go get nhooyr.io/ws@master
1515
```
1616

17-
## Why
17+
## Example
18+
19+
### Server
20+
21+
```go
22+
func main() {
23+
fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
24+
c, err := ws.Accept(w, r,
25+
ws.AcceptSubprotocols("echo"),
26+
)
27+
if err != nil {
28+
log.Printf("server handshake failed: %v", err)
29+
return
30+
}
31+
defer c.Close(ws.StatusInternalError, "")
32+
33+
ctx, cancel := context.WithTimeout(r.Context(), time.Second*10)
34+
defer cancel()
35+
36+
type myJsonStruct struct {
37+
MyField string `json:"my_field"`
38+
}
39+
err = wsjson.Write(ctx, c, myJsonStruct{
40+
MyField: "foo",
41+
})
42+
if err != nil {
43+
log.Printf("failed to write json struct: %v", err)
44+
return
45+
}
46+
47+
c.Close(ws.StatusNormalClosure, "")
48+
})
49+
// For production deployments, use a net/http.Server configured
50+
// with the appropriate timeouts.
51+
err := http.ListenAndServe("localhost:8080", fn)
52+
if err != nil {
53+
log.Fatalf("failed to listen and serve: %v", err)
54+
}
55+
}
56+
```
1857

19-
There is no other Go WebSocket library with a clean API.
58+
### Client
59+
60+
```go
61+
func main() {
62+
ctx := context.Background()
63+
ctx, cancel := context.WithTimeout(ctx, time.Minute)
64+
defer cancel()
65+
66+
c, _, err := ws.Dial(ctx, "ws://localhost:8080")
67+
if err != nil {
68+
log.Fatalf("failed to ws dial: %v", err)
69+
}
70+
defer c.Close(ws.StatusInternalError, "")
71+
72+
type myJsonStruct struct {
73+
MyField string `json:"my_field"`
74+
}
75+
err = wsjson.Write(ctx, c, myJsonStruct{
76+
MyField: "foo",
77+
})
78+
if err != nil {
79+
log.Fatalf("failed to write json struct: %v", err)
80+
}
81+
82+
c.Close(ws.StatusNormalClosure, "")
83+
}
84+
```
2085

21-
Comparisons with existing WebSocket libraries below.
86+
See [example_test.go](example_test.go) for more examples.
2287

23-
### [x/net/websocket](https://godoc.org/golang.org/x/net/websocket)
88+
## Features
2489

90+
- Full support of the WebSocket protocol
91+
- Simple to use because of the minimal API
92+
- Uses the context package for cancellation
93+
- Uses net/http's Client to do WebSocket dials
94+
- JSON and Protobuf helpers in wsjson and wspb subpackages
95+
- Compression extension is supported
96+
- Highly optimized
97+
- API will be ready for WebSockets over HTTP/2
2598

26-
Unmaintained and the API does not reflect WebSocket semantics.
99+
## Design considerations
27100

28-
See https://github.com/golang/go/issues/18152
101+
- Minimal API is easier to maintain and for others to learn
102+
- Context based cancellation is more ergonomic and robust than setting deadlines
103+
- No pings or pongs because TCP keep alives work fine for HTTP/1.1 and they do not make
104+
sense with HTTP/2
105+
- net.Conn is never exposed as WebSocket's over HTTP/2 will not have a net.Conn.
106+
- Functional options make the API very clean and easy to extend
107+
- Compression is very useful for JSON payloads
108+
- Protobuf and JSON helpers make code terse
109+
- Using net/http's Client for dialing means we do not have to reinvent dialing hooks
110+
and configurations. Just pass in a custom net/http client if you want custom dialing.
111+
112+
## Comparison
29113

30114
### [gorilla/websocket](https://github.com/gorilla/websocket)
31115

32-
This package is the community standard but it is very old and over time
116+
This package is the community standard but it is very old and over timennn
33117
has accumulated cruft. There are many ways to do the same thing and the API
34-
overall is just not very clear.
118+
overall is just not very clear. Just compare the godoc of
119+
[nhooyr/ws](godoc.org/github.com/nhooyr/ws) side by side with
120+
[gorilla/websocket](godoc.org/github.com/gorilla/websocket).
35121

36-
The callback hooks . The API for this library has been designed
37-
such that there is only one way to do things and callbacks have been avoided.
122+
The API for nhooyr/ws has been designed such that there is only one way to do things
123+
and with HTTP/2 in mind which makes using it correctly and safely much easier.
38124

39-
Performance sensitive applications should use ws/wscore directly.
125+
### [x/net/websocket](https://godoc.org/golang.org/x/net/websocket)
126+
127+
Unmaintained and the API does not reflect WebSocket semantics. Should never be used.
128+
129+
See https://github.com/golang/go/issues/18152
40130

41131
### [gobwas/ws](https://github.com/gobwas/ws)
42132

43-
This library has an extremely flexible API but that comes at a cost of usability
44-
and clarity. Its just not clear and simple how to do things in a safe manner.
133+
This library has an extremely flexible API but that comes at the cost of usability
134+
and clarity. Its just not clear how to do things in a safe manner.
45135

46-
## TODO
136+
This library is fantastic in terms of performance though. The author put in significant
137+
effort to ensure its speed and I have tried to apply as many of its teachings as
138+
I could into nhooyr/ws.
47139

48-
- [ ] Fully implement.
49-
- [ ] Decide whether we want to do WebSocket pings by default maybe every 30s?
50-
- [ ]
140+
If you want a library that gives you absolute control over everything, this is the library,
141+
but for most users, the API provided by nhooyr/ws will definitely fit better as it will
142+
be just as performant but much easier to use.

accept.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ func AcceptSubprotocols(subprotocols ...string) AcceptOption {
2424
// Use this option with caution to avoid exposing your WebSocket
2525
// server to a CSRF attack.
2626
// See https://stackoverflow.com/a/37837709/4283659
27+
// You can use a * to specify wildcards in domain names.
2728
func AcceptOrigins(origins ...string) AcceptOption {
2829
panic("TODO")
2930
}

datatype.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
package ws
22

3-
import (
4-
"nhooyr.io/ws/wscore"
5-
)
6-
73
// DataType represents the Opcode of a WebSocket data frame.
84
//go:generate stringer -type=DataType
95
type DataType int
106

117
// DataType constants.
128
const (
13-
Text DataType = DataType(wscore.OpText)
14-
Binary DataType = DataType(wscore.OpBinary)
9+
Text DataType = DataType(opText)
10+
Binary DataType = DataType(opBinary)
1511
)

dial.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ func DialSubprotocols(subprotocols ...string) DialOption {
3333

3434
// We use this key for all client requests as the Sec-WebSocket-Key header is useless.
3535
// See https://stackoverflow.com/a/37074398/4283659.
36+
// We also use the same mask key for every message as it too does not make a difference.
3637
var secWebSocketKey = base64.StdEncoding.EncodeToString(make([]byte, 16))
3738

38-
// Dial performs a websocket handshake on the given url with the given options.
39+
// Dial performs a WebSocket handshake on the given url with the given options.
3940
func Dial(ctx context.Context, u string, opts ...DialOption) (*Conn, *http.Response, error) {
4041
panic("TODO")
4142
}

doc.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1-
// Package ws implements the WebSocket protocol defined in RFC 6455.
1+
// Package ws is a minimal and idiomatic implementation of the WebSocket protocol.
2+
//
3+
// For now the docs are at https://github.com/nhooyr/ws#ws. I will move them here later.
24
package ws

example_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ func ExampleAccept_echo() {
4141
r.SetContext(ctx)
4242
r.Limit(32768)
4343

44-
w := c.MessageWriter(ctx, typ)
44+
w := c.MessageWriter(typ)
45+
w.SetContext(ctx)
4546
_, err = io.Copy(w, r)
4647
if err != nil {
4748
return err
@@ -83,10 +84,13 @@ func ExampleAccept() {
8384
}
8485
defer c.Close(ws.StatusInternalError, "")
8586

87+
ctx, cancel := context.WithTimeout(r.Context(), time.Second*10)
88+
defer cancel()
89+
8690
type myJsonStruct struct {
8791
MyField string `json:"my_field"`
8892
}
89-
err = wsjson.Write(r.Context(), c, myJsonStruct{
93+
err = wsjson.Write(ctx, c, myJsonStruct{
9094
MyField: "foo",
9195
})
9296
if err != nil {
@@ -112,7 +116,6 @@ func ExampleDial() {
112116
c, _, err := ws.Dial(ctx, "ws://localhost:8080")
113117
if err != nil {
114118
log.Fatalf("failed to ws dial: %v", err)
115-
return
116119
}
117120
defer c.Close(ws.StatusInternalError, "")
118121

@@ -124,7 +127,6 @@ func ExampleDial() {
124127
})
125128
if err != nil {
126129
log.Fatalf("failed to write json struct: %v", err)
127-
return
128130
}
129131

130132
c.Close(ws.StatusNormalClosure, "")

mask.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package ws
22

3-
// Mask applies the websocket masking algorithm to p
3+
// Mask applies the WebSocket masking algorithm to p
44
// with the given key where the first 3 bits of pos
55
// are the starting position in the key.
66
// See https://tools.ietf.org/html/rfc6455#section-5.3

opcode_string.go

Lines changed: 15 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ws.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package ws
22

33
import (
44
"context"
5-
"net"
65
)
76

87
const (
@@ -18,16 +17,11 @@ func (c *Conn) Subprotocol() string {
1817
panic("TODO")
1918
}
2019

21-
// NetConn returns the net.Conn underlying the Conn.
22-
func (c *Conn) NetConn() net.Conn {
23-
panic("TODO")
24-
}
25-
2620
// MessageWriter returns a writer bounded by the context that will write
2721
// a WebSocket data frame of type dataType to the connection.
2822
// Ensure you close the MessageWriter once you have written to entire message.
2923
// Concurrent calls to MessageWriter are ok.
30-
func (c *Conn) MessageWriter(ctx context.Context, dataType DataType) *MessageWriter {
24+
func (c *Conn) MessageWriter(dataType DataType) *MessageWriter {
3125
panic("TODO")
3226
}
3327

@@ -41,6 +35,9 @@ func (c *Conn) ReadMessage(ctx context.Context) (DataType, *MessageReader, error
4135
// Close closes the WebSocket connection with the given status code and reason.
4236
// It will write a WebSocket close frame with a timeout of 5 seconds.
4337
func (c *Conn) Close(code StatusCode, reason string) error {
38+
// This function also will not wait for a close frame from the peer like the RFC
39+
// wants because that makes no sense and I don't think anyone actually follows that.
40+
// Definitely worth seeing what popular browsers do later.
4441
panic("TODO")
4542
}
4643

@@ -56,6 +53,18 @@ func (w *MessageWriter) Write(p []byte) (n int, err error) {
5653
panic("TODO")
5754
}
5855

56+
// SetContext bounds the writer to the context.
57+
// This must be called before any write.
58+
func (w *MessageWriter) SetContext(ctx context.Context) {
59+
panic("TODO")
60+
}
61+
62+
// Compress marks the message to be compressed.
63+
// This must be called before any write.
64+
func (w *MessageWriter) Compress() {
65+
panic("TODO")
66+
}
67+
5968
// Close flushes the frame to the connection.
6069
// This must be called for every MessageWriter.
6170
func (w *MessageWriter) Close() error {
@@ -68,11 +77,13 @@ type MessageReader struct{}
6877
// SetContext bounds the read operation to the ctx.
6978
// By default, the context is the one passed to conn.ReadMessage.
7079
// You still almost always want a separate context for reading the message though.
80+
// Must be called before any read.
7181
func (r *MessageReader) SetContext(ctx context.Context) {
7282
panic("TODO")
7383
}
7484

7585
// Limit limits the number of bytes read by the reader.
86+
// Must be called before any read.
7687
func (r *MessageReader) Limit(bytes int) {
7788
panic("TODO")
7889
}

0 commit comments

Comments
 (0)