Skip to content

Commit b2cc511

Browse files
authored
Merge pull request #63 from nhooyr/ready
Docs and API improvements
2 parents a3c894c + c83f18f commit b2cc511

30 files changed

+443
-321
lines changed

.github/main.workflow

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
workflow "main" {
22
on = "push"
3-
resolves = ["fmt", "lint", "test"]
3+
resolves = ["fmt", "lint", "test", "bench"]
44
}
55

66
action "lint" {
7-
uses = "./.github/lint"
7+
uses = "./ci/lint"
88
}
99

1010
action "fmt" {
11-
uses = "./.github/fmt"
11+
uses = "./ci/fmt"
1212
}
1313

1414
action "test" {
15-
uses = "./.github/test"
15+
uses = "./ci/test"
1616
secrets = ["CODECOV_TOKEN"]
1717
}
18+
19+
action "bench" {
20+
uses = "./ci/bench"
21+
}

.github/test/entrypoint.sh

Lines changed: 0 additions & 17 deletions
This file was deleted.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
coverage.html
22
wstest_reports
33
websocket.test
4+
profs

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44

55
websocket is a minimal and idiomatic WebSocket library for Go.
66

7-
This library is in heavy development.
7+
At minimum Go 1.12 is required as websocket uses a new [feature](https://github.com/golang/go/issues/26937#issuecomment-415855861) in net/http
8+
to perform WebSocket handshakes.
9+
10+
This library is not final and the API is subject to change.
11+
12+
If you have any feedback, please feel free to open an issue.
813

914
## Install
1015

@@ -15,8 +20,8 @@ go get nhooyr.io/websocket
1520
## Features
1621

1722
- Full support of the WebSocket protocol
18-
- Only depends on stdlib
19-
- Simple to use
23+
- Zero dependencies outside of the stdlib
24+
- Very minimal and carefully considered API
2025
- context.Context is first class
2126
- net/http is used for WebSocket dials and upgrades
2227
- Thoroughly tested, fully passes the [autobahn-testsuite](https://github.com/crossbario/autobahn-testsuite)
@@ -26,9 +31,6 @@ go get nhooyr.io/websocket
2631

2732
- [ ] WebSockets over HTTP/2 [#4](https://github.com/nhooyr/websocket/issues/4)
2833
- [ ] Deflate extension support [#5](https://github.com/nhooyr/websocket/issues/5)
29-
- [ ] More optimization [#11](https://github.com/nhooyr/websocket/issues/11)
30-
- [ ] WASM [#15](https://github.com/nhooyr/websocket/issues/15)
31-
- [ ] Ping/pongs [#1](https://github.com/nhooyr/websocket/issues/1)
3234

3335
## Example
3436

accept.go

Lines changed: 43 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -12,72 +12,60 @@ import (
1212
"golang.org/x/xerrors"
1313
)
1414

15-
// AcceptOption is an option that can be passed to Accept.
16-
// The implementations of this interface are printable.
17-
type AcceptOption interface {
18-
acceptOption()
19-
}
20-
21-
type acceptSubprotocols []string
22-
23-
func (o acceptSubprotocols) acceptOption() {}
24-
25-
// AcceptSubprotocols lists the websocket subprotocols that Accept will negotiate with a client.
26-
// The empty subprotocol will always be negotiated as per RFC 6455. If you would like to
27-
// reject it, close the connection if c.Subprotocol() == "".
28-
func AcceptSubprotocols(protocols ...string) AcceptOption {
29-
return acceptSubprotocols(protocols)
30-
}
31-
32-
type acceptInsecureOrigin struct{}
33-
34-
func (o acceptInsecureOrigin) acceptOption() {}
35-
36-
// AcceptInsecureOrigin disables Accept's origin verification
37-
// behaviour. By default Accept only allows the handshake to
38-
// succeed if the javascript that is initiating the handshake
39-
// is on the same domain as the server. This is to prevent CSRF
40-
// when secure data is stored in cookies.
41-
//
42-
// See https://stackoverflow.com/a/37837709/4283659
43-
//
44-
// Use this if you want a WebSocket server any javascript can
45-
// connect to or you want to perform Origin verification yourself
46-
// and allow some whitelist of domains.
47-
//
48-
// Ensure you understand exactly what the above means before you use
49-
// this option in conjugation with cookies containing secure data.
50-
func AcceptInsecureOrigin() AcceptOption {
51-
return acceptInsecureOrigin{}
15+
// AcceptOptions represents the options available to pass to Accept.
16+
type AcceptOptions struct {
17+
// Subprotocols lists the websocket subprotocols that Accept will negotiate with a client.
18+
// The empty subprotocol will always be negotiated as per RFC 6455. If you would like to
19+
// reject it, close the connection if c.Subprotocol() == "".
20+
Subprotocols []string
21+
22+
// InsecureSkipVerify disables Accept's origin verification
23+
// behaviour. By default Accept only allows the handshake to
24+
// succeed if the javascript that is initiating the handshake
25+
// is on the same domain as the server. This is to prevent CSRF
26+
// when secure data is stored in a cookie as there is no same
27+
// origin policy for WebSockets. In other words, javascript from
28+
// any domain can perform a WebSocket dial on an arbitrary server.
29+
// This dial will include cookies which means the arbitrary javascript
30+
// can perform actions as the authenticated user.
31+
//
32+
// See https://stackoverflow.com/a/37837709/4283659
33+
//
34+
// The only time you need this is if your javascript is running on a different domain
35+
// than your WebSocket server.
36+
// 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.
39+
InsecureSkipVerify bool
5240
}
5341

5442
func verifyClientRequest(w http.ResponseWriter, r *http.Request) error {
5543
if !headerValuesContainsToken(r.Header, "Connection", "Upgrade") {
56-
err := xerrors.Errorf("websocket: protocol violation: Connection header %q does not contain Upgrade", r.Header.Get("Connection"))
44+
err := xerrors.Errorf("websocket protocol violation: Connection header %q does not contain Upgrade", r.Header.Get("Connection"))
5745
http.Error(w, err.Error(), http.StatusBadRequest)
5846
return err
5947
}
6048

6149
if !headerValuesContainsToken(r.Header, "Upgrade", "WebSocket") {
62-
err := xerrors.Errorf("websocket: protocol violation: Upgrade header %q does not contain websocket", r.Header.Get("Upgrade"))
50+
err := xerrors.Errorf("websocket protocol violation: Upgrade header %q does not contain websocket", r.Header.Get("Upgrade"))
6351
http.Error(w, err.Error(), http.StatusBadRequest)
6452
return err
6553
}
6654

6755
if r.Method != "GET" {
68-
err := xerrors.Errorf("websocket: protocol violation: handshake request method %q is not GET", r.Method)
56+
err := xerrors.Errorf("websocket protocol violation: handshake request method %q is not GET", r.Method)
6957
http.Error(w, err.Error(), http.StatusBadRequest)
7058
return err
7159
}
7260

7361
if r.Header.Get("Sec-WebSocket-Version") != "13" {
74-
err := xerrors.Errorf("websocket: unsupported protocol version: %q", r.Header.Get("Sec-WebSocket-Version"))
62+
err := xerrors.Errorf("unsupported websocket protocol version: %q", r.Header.Get("Sec-WebSocket-Version"))
7563
http.Error(w, err.Error(), http.StatusBadRequest)
7664
return err
7765
}
7866

7967
if r.Header.Get("Sec-WebSocket-Key") == "" {
80-
err := xerrors.New("websocket: protocol violation: missing Sec-WebSocket-Key")
68+
err := xerrors.New("websocket protocol violation: missing Sec-WebSocket-Key")
8169
http.Error(w, err.Error(), http.StatusBadRequest)
8270
return err
8371
}
@@ -88,26 +76,22 @@ func verifyClientRequest(w http.ResponseWriter, r *http.Request) error {
8876
// Accept accepts a WebSocket handshake from a client and upgrades the
8977
// the connection to WebSocket.
9078
// Accept will reject the handshake if the Origin is not the same as the Host unless
91-
// the AcceptInsecureOrigin option is passed.
92-
// Accept uses w to write the handshake response so the timeouts on the http.Server apply.
93-
func Accept(w http.ResponseWriter, r *http.Request, opts ...AcceptOption) (*Conn, error) {
94-
var subprotocols []string
95-
verifyOrigin := true
96-
for _, opt := range opts {
97-
switch opt := opt.(type) {
98-
case acceptInsecureOrigin:
99-
verifyOrigin = false
100-
case acceptSubprotocols:
101-
subprotocols = []string(opt)
102-
}
79+
// the InsecureSkipVerify option is set.
80+
func Accept(w http.ResponseWriter, r *http.Request, opts AcceptOptions) (*Conn, error) {
81+
c, err := accept(w, r, opts)
82+
if err != nil {
83+
return nil, xerrors.Errorf("failed to accept websocket connection: %w", err)
10384
}
85+
return c, nil
86+
}
10487

88+
func accept(w http.ResponseWriter, r *http.Request, opts AcceptOptions) (*Conn, error) {
10589
err := verifyClientRequest(w, r)
10690
if err != nil {
10791
return nil, err
10892
}
10993

110-
if verifyOrigin {
94+
if !opts.InsecureSkipVerify {
11195
err = authenticateOrigin(r)
11296
if err != nil {
11397
http.Error(w, err.Error(), http.StatusForbidden)
@@ -117,7 +101,7 @@ func Accept(w http.ResponseWriter, r *http.Request, opts ...AcceptOption) (*Conn
117101

118102
hj, ok := w.(http.Hijacker)
119103
if !ok {
120-
err = xerrors.New("websocket: response writer does not implement http.Hijacker")
104+
err = xerrors.New("response writer must implement http.Hijacker")
121105
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
122106
return nil, err
123107
}
@@ -127,7 +111,7 @@ func Accept(w http.ResponseWriter, r *http.Request, opts ...AcceptOption) (*Conn
127111

128112
handleKey(w, r)
129113

130-
subproto := selectSubprotocol(r, subprotocols)
114+
subproto := selectSubprotocol(r, opts.Subprotocols)
131115
if subproto != "" {
132116
w.Header().Set("Sec-WebSocket-Protocol", subproto)
133117
}
@@ -136,7 +120,7 @@ func Accept(w http.ResponseWriter, r *http.Request, opts ...AcceptOption) (*Conn
136120

137121
netConn, brw, err := hj.Hijack()
138122
if err != nil {
139-
err = xerrors.Errorf("websocket: failed to hijack connection: %w", err)
123+
err = xerrors.Errorf("failed to hijack connection: %w", err)
140124
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
141125
return nil, err
142126
}
@@ -190,5 +174,5 @@ func authenticateOrigin(r *http.Request) error {
190174
if strings.EqualFold(u.Host, r.Host) {
191175
return nil
192176
}
193-
return xerrors.Errorf("request origin %q is not authorized", origin)
177+
return xerrors.Errorf("request origin %q is not authorized for host %q", origin, r.Host)
194178
}

0 commit comments

Comments
 (0)