Skip to content

Commit b97c4ae

Browse files
authored
Merge pull request #31 from nhooyr/docs
Remove wscore and improve docs
2 parents 662a1b8 + 2b1f537 commit b97c4ae

File tree

13 files changed

+213
-104
lines changed

13 files changed

+213
-104
lines changed

README.md

Lines changed: 113 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +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 are also confusing. 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.
135+
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.
139+
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, "")

wscore/header.go renamed to header.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
package wscore
1+
package ws
22

33
import (
44
"io"
55
)
66

7-
// Header represents a WebSocket frame header.
7+
// header represents a WebSocket frame header.
88
// See https://tools.ietf.org/html/rfc6455#section-5.2
9-
type Header struct {
9+
// The fields are exported for easy printing for debugging.
10+
type header struct {
1011
Fin bool
1112
Rsv1 bool
1213
Rsv2 bool
1314
Rsv3 bool
14-
Opcode Opcode
15+
Opcode opcode
1516

1617
PayloadLength int64
1718

@@ -20,7 +21,7 @@ type Header struct {
2021
}
2122

2223
// Bytes returns the bytes of the header.
23-
func (h Header) Bytes() []byte {
24+
func (h header) Bytes() []byte {
2425
panic("TODO")
2526
}
2627

wscore/mask.go renamed to mask.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
package wscore
1+
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
@@ -10,6 +10,6 @@ package wscore
1010
//
1111
// For targets that do not support unsafe, please report an issue.
1212
// There is a mask by byte function below that will be used for such targets.
13-
func Mask(key [4]byte, pos int, p []byte) int {
13+
func mask(key [4]byte, pos int, p []byte) int {
1414
panic("TODO")
1515
}

opcode.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package ws
2+
3+
// opcode represents a WebSocket Opcode.
4+
//go:generate stringer -type=opcode
5+
type opcode int
6+
7+
// opcode constants.
8+
const (
9+
opContinuation opcode = iota
10+
opText
11+
opBinary
12+
// 3 - 7 are reserved for further non-control frames.
13+
opClose opcode = 8 + iota
14+
opPing
15+
opPong
16+
// 11-16 are reserved for further control frames.
17+
)

opcode_string.go

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

0 commit comments

Comments
 (0)