Skip to content

Update master with dev #222

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Apr 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e8f5bc8
Add Example_crossOrigin
nhooyr Feb 21, 2020
fa720b9
Merge pull request #195 from nhooyr/example-cross-origin
nhooyr Feb 21, 2020
500b9d7
Add OriginPatterns to AcceptOptions
nhooyr Feb 26, 2020
97345d8
Simplify wstest.Pipe
nhooyr Feb 26, 2020
108d760
Merge pull request #198 from nhooyr/accept
nhooyr Feb 27, 2020
e92b547
Merge pull request #202 from nhooyr/wstest
nhooyr Feb 27, 2020
deb14cf
Make sure to release lock when acquiring and connection is closed.
nhooyr Feb 27, 2020
97172f3
Add Grace to gracefully close WebSocket connections
nhooyr Feb 26, 2020
e335b09
Use grace in chat example
nhooyr Feb 26, 2020
190981d
Add automated test to chat example
nhooyr Feb 26, 2020
da3aa8c
Improve chat example test
nhooyr Feb 27, 2020
0d9471d
Merge pull request #200 from nhooyr/server
nhooyr Feb 27, 2020
07343c2
Allow passing http:// and https:// URLS to Dial
nhooyr Feb 28, 2020
008b616
Remove Grace partially
nhooyr Mar 22, 2020
b307b47
Improve docs and fix examples
nhooyr Apr 14, 2020
f7ef6b8
Remove grace for now
nhooyr Apr 14, 2020
98779ee
Fix outdated close handshake docs
nhooyr Apr 14, 2020
5db7b71
Clarify CloseRead docs
nhooyr Apr 14, 2020
d0fa6bf
Update prettier invocation for v2.0.0
nhooyr Apr 14, 2020
ee1fed8
Merge branch 'master' into dev
nhooyr Apr 14, 2020
ba35516
Doc fixes
nhooyr Apr 14, 2020
c4d4650
Fix bad close handshake logic
nhooyr Apr 14, 2020
d34c89a
Prevent all writes after close
nhooyr Apr 14, 2020
2dc66c3
Check whether the connection is closed before returning a write IO error
nhooyr Apr 14, 2020
1d80cf3
Final doc fixes
nhooyr Apr 14, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 21 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# websocket

[![godoc](https://godoc.org/nhooyr.io/websocket?status.svg)](https://godoc.org/nhooyr.io/websocket)
[![godoc](https://godoc.org/nhooyr.io/websocket?status.svg)](https://pkg.go.dev/nhooyr.io/websocket)

websocket is a minimal and idiomatic WebSocket library for Go.

Expand All @@ -15,26 +15,27 @@ go get nhooyr.io/websocket
- Minimal and idiomatic API
- First class [context.Context](https://blog.golang.org/context) support
- Fully passes the WebSocket [autobahn-testsuite](https://github.com/crossbario/autobahn-testsuite)
- Thorough unit tests with [90% coverage](https://coveralls.io/github/nhooyr/websocket)
- [Minimal dependencies](https://godoc.org/nhooyr.io/websocket?imports)
- JSON and protobuf helpers in the [wsjson](https://godoc.org/nhooyr.io/websocket/wsjson) and [wspb](https://godoc.org/nhooyr.io/websocket/wspb) subpackages
- Thorough tests with [90% coverage](https://coveralls.io/github/nhooyr/websocket)
- [Minimal dependencies](https://pkg.go.dev/nhooyr.io/websocket?tab=imports)
- JSON and protobuf helpers in the [wsjson](https://pkg.go.dev/nhooyr.io/websocket/wsjson) and [wspb](https://pkg.go.dev/nhooyr.io/websocket/wspb) subpackages
- Zero alloc reads and writes
- Concurrent writes
- [Close handshake](https://godoc.org/nhooyr.io/websocket#Conn.Close)
- [net.Conn](https://godoc.org/nhooyr.io/websocket#NetConn) wrapper
- [Ping pong](https://godoc.org/nhooyr.io/websocket#Conn.Ping) API
- [Close handshake](https://pkg.go.dev/nhooyr.io/websocket#Conn.Close)
- [net.Conn](https://pkg.go.dev/nhooyr.io/websocket#NetConn) wrapper
- [Ping pong](https://pkg.go.dev/nhooyr.io/websocket#Conn.Ping) API
- [RFC 7692](https://tools.ietf.org/html/rfc7692) permessage-deflate compression
- Compile to [Wasm](https://godoc.org/nhooyr.io/websocket#hdr-Wasm)
- Compile to [Wasm](https://pkg.go.dev/nhooyr.io/websocket#hdr-Wasm)

## Roadmap

- [ ] HTTP/2 [#4](https://github.com/nhooyr/websocket/issues/4)

## Examples

For a production quality example that demonstrates the complete API, see the [echo example](https://godoc.org/nhooyr.io/websocket#example-package--Echo).
For a production quality example that demonstrates the complete API, see the
[echo example](./examples/echo).

For a full stack example, see [./chat-example](./chat-example).
For a full stack example, see the [chat example](./examples/chat).

### Server

Expand Down Expand Up @@ -88,39 +89,39 @@ c.Close(websocket.StatusNormalClosure, "")
Advantages of [gorilla/websocket](https://github.com/gorilla/websocket):

- Mature and widely used
- [Prepared writes](https://godoc.org/github.com/gorilla/websocket#PreparedMessage)
- Configurable [buffer sizes](https://godoc.org/github.com/gorilla/websocket#hdr-Buffers)
- [Prepared writes](https://pkg.go.dev/github.com/gorilla/websocket#PreparedMessage)
- Configurable [buffer sizes](https://pkg.go.dev/github.com/gorilla/websocket#hdr-Buffers)

Advantages of nhooyr.io/websocket:

- Minimal and idiomatic API
- Compare godoc of [nhooyr.io/websocket](https://godoc.org/nhooyr.io/websocket) with [gorilla/websocket](https://godoc.org/github.com/gorilla/websocket) side by side.
- [net.Conn](https://godoc.org/nhooyr.io/websocket#NetConn) wrapper
- Compare godoc of [nhooyr.io/websocket](https://pkg.go.dev/nhooyr.io/websocket) with [gorilla/websocket](https://pkg.go.dev/github.com/gorilla/websocket) side by side.
- [net.Conn](https://pkg.go.dev/nhooyr.io/websocket#NetConn) wrapper
- Zero alloc reads and writes ([gorilla/websocket#535](https://github.com/gorilla/websocket/issues/535))
- Full [context.Context](https://blog.golang.org/context) support
- Dial uses [net/http.Client](https://golang.org/pkg/net/http/#Client)
- Will enable easy HTTP/2 support in the future
- Gorilla writes directly to a net.Conn and so duplicates features of net/http.Client.
- Concurrent writes
- Close handshake ([gorilla/websocket#448](https://github.com/gorilla/websocket/issues/448))
- Idiomatic [ping pong](https://godoc.org/nhooyr.io/websocket#Conn.Ping) API
- Idiomatic [ping pong](https://pkg.go.dev/nhooyr.io/websocket#Conn.Ping) API
- Gorilla requires registering a pong callback before sending a Ping
- Can target Wasm ([gorilla/websocket#432](https://github.com/gorilla/websocket/issues/432))
- Transparent message buffer reuse with [wsjson](https://godoc.org/nhooyr.io/websocket/wsjson) and [wspb](https://godoc.org/nhooyr.io/websocket/wspb) subpackages
- Transparent message buffer reuse with [wsjson](https://pkg.go.dev/nhooyr.io/websocket/wsjson) and [wspb](https://pkg.go.dev/nhooyr.io/websocket/wspb) subpackages
- [1.75x](https://github.com/nhooyr/websocket/releases/tag/v1.7.4) faster WebSocket masking implementation in pure Go
- Gorilla's implementation is slower and uses [unsafe](https://golang.org/pkg/unsafe/).
- Full [permessage-deflate](https://tools.ietf.org/html/rfc7692) compression extension support
- Gorilla only supports no context takeover mode
- We use [klauspost/compress](https://github.com/klauspost/compress) for much lower memory usage ([gorilla/websocket#203](https://github.com/gorilla/websocket/issues/203))
- [CloseRead](https://godoc.org/nhooyr.io/websocket#Conn.CloseRead) helper ([gorilla/websocket#492](https://github.com/gorilla/websocket/issues/492))
- We use a vendored [klauspost/compress](https://github.com/klauspost/compress) for much lower memory usage ([gorilla/websocket#203](https://github.com/gorilla/websocket/issues/203))
- [CloseRead](https://pkg.go.dev/nhooyr.io/websocket#Conn.CloseRead) helper ([gorilla/websocket#492](https://github.com/gorilla/websocket/issues/492))
- Actively maintained ([gorilla/websocket#370](https://github.com/gorilla/websocket/issues/370))

#### golang.org/x/net/websocket

[golang.org/x/net/websocket](https://godoc.org/golang.org/x/net/websocket) is deprecated.
[golang.org/x/net/websocket](https://pkg.go.dev/golang.org/x/net/websocket) is deprecated.
See [golang/go/issues/18152](https://github.com/golang/go/issues/18152).

The [net.Conn](https://godoc.org/nhooyr.io/websocket#NetConn) wrapper will ease in transitioning
The [net.Conn](https://pkg.go.dev/nhooyr.io/websocket#NetConn) can help in transitioning
to nhooyr.io/websocket.

#### gobwas/ws
Expand Down
73 changes: 47 additions & 26 deletions accept.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import (
"errors"
"fmt"
"io"
"log"
"net/http"
"net/textproto"
"net/url"
"strconv"
"path/filepath"
"strings"

"nhooyr.io/websocket/internal/errd"
Expand All @@ -25,18 +26,27 @@ type AcceptOptions struct {
// reject it, close the connection when c.Subprotocol() == "".
Subprotocols []string

// InsecureSkipVerify disables Accept's origin verification behaviour. By default,
// the connection will only be accepted if the request origin is equal to the request
// host.
// InsecureSkipVerify is used to disable Accept's origin verification behaviour.
//
// This is only required if you want javascript served from a different domain
// to access your WebSocket server.
// Deprecated: Use OriginPatterns with a match all pattern of * instead to control
// origin authorization yourself.
InsecureSkipVerify bool

// OriginPatterns lists the host patterns for authorized origins.
// The request host is always authorized.
// Use this to enable cross origin WebSockets.
//
// i.e javascript running on example.com wants to access a WebSocket server at chat.example.com.
// In such a case, example.com is the origin and chat.example.com is the request host.
// One would set this field to []string{"example.com"} to authorize example.com to connect.
//
// See https://stackoverflow.com/a/37837709/4283659
// Each pattern is matched case insensitively against the request origin host
// with filepath.Match.
// See https://golang.org/pkg/path/filepath/#Match
//
// Please ensure you understand the ramifications of enabling this.
// If used incorrectly your WebSocket server will be open to CSRF attacks.
InsecureSkipVerify bool
OriginPatterns []string

// CompressionMode controls the compression mode.
// Defaults to CompressionNoContextTakeover.
Expand Down Expand Up @@ -77,8 +87,12 @@ func accept(w http.ResponseWriter, r *http.Request, opts *AcceptOptions) (_ *Con
}

if !opts.InsecureSkipVerify {
err = authenticateOrigin(r)
err = authenticateOrigin(r, opts.OriginPatterns)
if err != nil {
if errors.Is(err, filepath.ErrBadPattern) {
log.Printf("websocket: %v", err)
err = errors.New(http.StatusText(http.StatusForbidden))
}
http.Error(w, err.Error(), http.StatusForbidden)
return nil, err
}
Expand Down Expand Up @@ -165,18 +179,35 @@ func verifyClientRequest(w http.ResponseWriter, r *http.Request) (errCode int, _
return 0, nil
}

func authenticateOrigin(r *http.Request) error {
func authenticateOrigin(r *http.Request, originHosts []string) error {
origin := r.Header.Get("Origin")
if origin != "" {
u, err := url.Parse(origin)
if origin == "" {
return nil
}

u, err := url.Parse(origin)
if err != nil {
return fmt.Errorf("failed to parse Origin header %q: %w", origin, err)
}

if strings.EqualFold(r.Host, u.Host) {
return nil
}

for _, hostPattern := range originHosts {
matched, err := match(hostPattern, u.Host)
if err != nil {
return fmt.Errorf("failed to parse Origin header %q: %w", origin, err)
return fmt.Errorf("failed to parse filepath pattern %q: %w", hostPattern, err)
}
if !strings.EqualFold(u.Host, r.Host) {
return fmt.Errorf("request Origin %q is not authorized for Host %q", origin, r.Host)
if matched {
return nil
}
}
return nil
return fmt.Errorf("request Origin %q is not authorized for Host %q", origin, r.Host)
}

func match(pattern, s string) (bool, error) {
return filepath.Match(strings.ToLower(pattern), strings.ToLower(s))
}

func selectSubprotocol(r *http.Request, subprotocols []string) string {
Expand Down Expand Up @@ -235,16 +266,6 @@ func acceptDeflate(w http.ResponseWriter, ext websocketExtension, mode Compressi
return copts, nil
}

// parseExtensionParameter parses the value in the extension parameter p.
func parseExtensionParameter(p string) (int, bool) {
ps := strings.Split(p, "=")
if len(ps) == 1 {
return 0, false
}
i, e := strconv.Atoi(strings.Trim(ps[1], `"`))
return i, e == nil
}

func acceptWebkitDeflate(w http.ResponseWriter, ext websocketExtension, mode CompressionMode) (*compressionOptions, error) {
copts := mode.opts()
// The peer must explicitly request it.
Expand Down
1 change: 1 addition & 0 deletions accept_js.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
type AcceptOptions struct {
Subprotocols []string
InsecureSkipVerify bool
OriginPatterns []string
CompressionMode CompressionMode
CompressionThreshold int
}
Expand Down
31 changes: 26 additions & 5 deletions accept_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,11 @@ func Test_authenticateOrigin(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
origin string
host string
success bool
name string
origin string
host string
originPatterns []string
success bool
}{
{
name: "none",
Expand Down Expand Up @@ -278,6 +279,26 @@ func Test_authenticateOrigin(t *testing.T) {
host: "example.com",
success: true,
},
{
name: "originPatterns",
origin: "https://two.examplE.com",
host: "example.com",
originPatterns: []string{
"*.example.com",
"bar.com",
},
success: true,
},
{
name: "originPatternsUnauthorized",
origin: "https://two.examplE.com",
host: "example.com",
originPatterns: []string{
"exam3.com",
"bar.com",
},
success: false,
},
}

for _, tc := range testCases {
Expand All @@ -288,7 +309,7 @@ func Test_authenticateOrigin(t *testing.T) {
r := httptest.NewRequest("GET", "http://"+tc.host+"/", nil)
r.Header.Set("Origin", tc.origin)

err := authenticateOrigin(r)
err := authenticateOrigin(r, tc.originPatterns)
if tc.success {
assert.Success(t, err)
} else {
Expand Down
Loading