Skip to content

Remove extra dependencies #135

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 4 commits into from
Sep 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: 2
jobs:
fmt:
docker:
- image: nhooyr/websocket-ci@sha256:371ca985ce2548840aeb0f8434a551708cdfe0628be722c361958e65cdded945
- image: nhooyr/websocket-ci@sha256:77e37211ded3c528e947439e294fbfc03b4fb9f9537c4e5198d5b304fd1df435
steps:
- checkout
- restore_cache:
Expand All @@ -19,7 +19,7 @@ jobs:

lint:
docker:
- image: nhooyr/websocket-ci@sha256:371ca985ce2548840aeb0f8434a551708cdfe0628be722c361958e65cdded945
- image: nhooyr/websocket-ci@sha256:77e37211ded3c528e947439e294fbfc03b4fb9f9537c4e5198d5b304fd1df435
steps:
- checkout
- restore_cache:
Expand All @@ -36,7 +36,7 @@ jobs:

test:
docker:
- image: nhooyr/websocket-ci@sha256:371ca985ce2548840aeb0f8434a551708cdfe0628be722c361958e65cdded945
- image: nhooyr/websocket-ci@sha256:77e37211ded3c528e947439e294fbfc03b4fb9f9537c4e5198d5b304fd1df435
steps:
- checkout
- restore_cache:
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ websocket is a minimal and idiomatic WebSocket library for Go.
## Install

```bash
go get nhooyr.io/[email protected].0
go get nhooyr.io/[email protected].1
```

## Features

- Minimal and idiomatic API
- Tiny codebase at 1700 lines
- First class context.Context support
- First class [context.Context](https://blog.golang.org/context) support
- Thorough tests, fully passes the [autobahn-testsuite](https://github.com/crossbario/autobahn-testsuite)
- Zero dependencies outside of the stdlib for the core library
- JSON and ProtoBuf helpers in the wsjson and wspb subpackages
- [Zero 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
- Highly optimized by default
- Concurrent writes out of the box

Expand All @@ -32,7 +32,7 @@ go get nhooyr.io/[email protected]

For a production quality example that shows off the full API, see the [echo example on the godoc](https://godoc.org/nhooyr.io/websocket#example-package--Echo). On github, the example is at [example_echo_test.go](./example_echo_test.go).

Please use the [golang.org/x/xerrors.As](https://godoc.org/golang.org/x/xerrors#As) package to check for [websocket.CloseError](https://godoc.org/nhooyr.io/websocket#CloseError). See the [CloseError godoc example](https://godoc.org/nhooyr.io/websocket#example-CloseError).
Please use the [errors.As](https://golang.org/pkg/errors/#As) function [new in Go 1.13](https://golang.org/doc/go1.13#error_wrapping) to check for [websocket.CloseError](https://godoc.org/nhooyr.io/websocket#CloseError). See the [CloseError godoc example](https://godoc.org/nhooyr.io/websocket#example-CloseError).

### Server

Expand Down Expand Up @@ -172,4 +172,4 @@ This is a list of companies or projects that use this library.

- [Coder](https://github.com/cdr)

If your company or project is using this library, please feel free to open a PR to amend the list.
If your company or project is using this library, please feel free to open an issue or PR to amend the list.
49 changes: 34 additions & 15 deletions accept.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import (
"bytes"
"crypto/sha1"
"encoding/base64"
"errors"
"fmt"
"io"
"net/http"
"net/textproto"
"net/url"
"strings"

"golang.org/x/net/http/httpguts"
"golang.org/x/xerrors"
)

// AcceptOptions represents the options available to pass to Accept.
Expand Down Expand Up @@ -43,31 +42,31 @@ type AcceptOptions struct {

func verifyClientRequest(w http.ResponseWriter, r *http.Request) error {
if !headerValuesContainsToken(r.Header, "Connection", "Upgrade") {
err := xerrors.Errorf("websocket protocol violation: Connection header %q does not contain Upgrade", r.Header.Get("Connection"))
err := fmt.Errorf("websocket protocol violation: Connection header %q does not contain Upgrade", r.Header.Get("Connection"))
http.Error(w, err.Error(), http.StatusBadRequest)
return err
}

if !headerValuesContainsToken(r.Header, "Upgrade", "WebSocket") {
err := xerrors.Errorf("websocket protocol violation: Upgrade header %q does not contain websocket", r.Header.Get("Upgrade"))
err := fmt.Errorf("websocket protocol violation: Upgrade header %q does not contain websocket", r.Header.Get("Upgrade"))
http.Error(w, err.Error(), http.StatusBadRequest)
return err
}

if r.Method != "GET" {
err := xerrors.Errorf("websocket protocol violation: handshake request method is not GET but %q", r.Method)
err := fmt.Errorf("websocket protocol violation: handshake request method is not GET but %q", r.Method)
http.Error(w, err.Error(), http.StatusBadRequest)
return err
}

if r.Header.Get("Sec-WebSocket-Version") != "13" {
err := xerrors.Errorf("unsupported websocket protocol version (only 13 is supported): %q", r.Header.Get("Sec-WebSocket-Version"))
err := fmt.Errorf("unsupported websocket protocol version (only 13 is supported): %q", r.Header.Get("Sec-WebSocket-Version"))
http.Error(w, err.Error(), http.StatusBadRequest)
return err
}

if r.Header.Get("Sec-WebSocket-Key") == "" {
err := xerrors.New("websocket protocol violation: missing Sec-WebSocket-Key")
err := errors.New("websocket protocol violation: missing Sec-WebSocket-Key")
http.Error(w, err.Error(), http.StatusBadRequest)
return err
}
Expand All @@ -87,7 +86,7 @@ func verifyClientRequest(w http.ResponseWriter, r *http.Request) error {
func Accept(w http.ResponseWriter, r *http.Request, opts *AcceptOptions) (*Conn, error) {
c, err := accept(w, r, opts)
if err != nil {
return nil, xerrors.Errorf("failed to accept websocket connection: %w", err)
return nil, fmt.Errorf("failed to accept websocket connection: %w", err)
}
return c, nil
}
Expand All @@ -112,7 +111,7 @@ func accept(w http.ResponseWriter, r *http.Request, opts *AcceptOptions) (*Conn,

hj, ok := w.(http.Hijacker)
if !ok {
err = xerrors.New("passed ResponseWriter does not implement http.Hijacker")
err = errors.New("passed ResponseWriter does not implement http.Hijacker")
http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented)
return nil, err
}
Expand All @@ -131,7 +130,7 @@ func accept(w http.ResponseWriter, r *http.Request, opts *AcceptOptions) (*Conn,

netConn, brw, err := hj.Hijack()
if err != nil {
err = xerrors.Errorf("failed to hijack connection: %w", err)
err = fmt.Errorf("failed to hijack connection: %w", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return nil, err
}
Expand All @@ -151,9 +150,29 @@ func accept(w http.ResponseWriter, r *http.Request, opts *AcceptOptions) (*Conn,
return c, nil
}

func headerValuesContainsToken(h http.Header, key, val string) bool {
func headerValuesContainsToken(h http.Header, key, token string) bool {
key = textproto.CanonicalMIMEHeaderKey(key)
return httpguts.HeaderValuesContainsToken(h[key], val)

for _, val2 := range h[key] {
if headerValueContainsToken(val2, token) {
return true
}
}

return false
}

func headerValueContainsToken(val2, token string) bool {
val2 = strings.TrimSpace(val2)

for _, val2 := range strings.Split(val2, ",") {
val2 = strings.TrimSpace(val2)
if strings.EqualFold(val2, token) {
return true
}
}

return false
}

func selectSubprotocol(r *http.Request, subprotocols []string) string {
Expand Down Expand Up @@ -187,10 +206,10 @@ func authenticateOrigin(r *http.Request) error {
}
u, err := url.Parse(origin)
if err != nil {
return xerrors.Errorf("failed to parse Origin header %q: %w", origin, err)
return fmt.Errorf("failed to parse Origin header %q: %w", origin, err)
}
if strings.EqualFold(u.Host, r.Host) {
return nil
}
return xerrors.Errorf("request Origin %q is not authorized for Host %q", origin, r.Host)
return fmt.Errorf("request Origin %q is not authorized for Host %q", origin, r.Host)
}
23 changes: 11 additions & 12 deletions dial.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import (
"bytes"
"context"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"math/rand"
"net/http"
"net/url"
"strings"
"sync"

"golang.org/x/xerrors"
)

// DialOptions represents the options available to pass to Dial.
Expand Down Expand Up @@ -44,7 +43,7 @@ type DialOptions struct {
func Dial(ctx context.Context, u string, opts *DialOptions) (*Conn, *http.Response, error) {
c, r, err := dial(ctx, u, opts)
if err != nil {
return nil, r, xerrors.Errorf("failed to websocket dial: %w", err)
return nil, r, fmt.Errorf("failed to websocket dial: %w", err)
}
return c, r, nil
}
Expand All @@ -62,15 +61,15 @@ func dial(ctx context.Context, u string, opts *DialOptions) (_ *Conn, _ *http.Re
opts.HTTPClient = http.DefaultClient
}
if opts.HTTPClient.Timeout > 0 {
return nil, nil, xerrors.Errorf("please use context for cancellation instead of http.Client.Timeout; see https://github.com/nhooyr/websocket/issues/67")
return nil, nil, fmt.Errorf("please use context for cancellation instead of http.Client.Timeout; see https://github.com/nhooyr/websocket/issues/67")
}
if opts.HTTPHeader == nil {
opts.HTTPHeader = http.Header{}
}

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

switch parsedURL.Scheme {
Expand All @@ -79,7 +78,7 @@ func dial(ctx context.Context, u string, opts *DialOptions) (_ *Conn, _ *http.Re
case "wss":
parsedURL.Scheme = "https"
default:
return nil, nil, xerrors.Errorf("unexpected url scheme: %q", parsedURL.Scheme)
return nil, nil, fmt.Errorf("unexpected url scheme: %q", parsedURL.Scheme)
}

req, _ := http.NewRequest("GET", parsedURL.String(), nil)
Expand All @@ -95,7 +94,7 @@ func dial(ctx context.Context, u string, opts *DialOptions) (_ *Conn, _ *http.Re

resp, err := opts.HTTPClient.Do(req)
if err != nil {
return nil, nil, xerrors.Errorf("failed to send handshake request: %w", err)
return nil, nil, fmt.Errorf("failed to send handshake request: %w", err)
}
defer func() {
if err != nil {
Expand All @@ -114,7 +113,7 @@ func dial(ctx context.Context, u string, opts *DialOptions) (_ *Conn, _ *http.Re

rwc, ok := resp.Body.(io.ReadWriteCloser)
if !ok {
return nil, resp, xerrors.Errorf("response body is not a io.ReadWriteCloser: %T", rwc)
return nil, resp, fmt.Errorf("response body is not a io.ReadWriteCloser: %T", rwc)
}

c := &Conn{
Expand All @@ -132,19 +131,19 @@ func dial(ctx context.Context, u string, opts *DialOptions) (_ *Conn, _ *http.Re

func verifyServerResponse(r *http.Request, resp *http.Response) error {
if resp.StatusCode != http.StatusSwitchingProtocols {
return xerrors.Errorf("expected handshake response status code %v but got %v", http.StatusSwitchingProtocols, resp.StatusCode)
return fmt.Errorf("expected handshake response status code %v but got %v", http.StatusSwitchingProtocols, resp.StatusCode)
}

if !headerValuesContainsToken(resp.Header, "Connection", "Upgrade") {
return xerrors.Errorf("websocket protocol violation: Connection header %q does not contain Upgrade", resp.Header.Get("Connection"))
return fmt.Errorf("websocket protocol violation: Connection header %q does not contain Upgrade", resp.Header.Get("Connection"))
}

if !headerValuesContainsToken(resp.Header, "Upgrade", "WebSocket") {
return xerrors.Errorf("websocket protocol violation: Upgrade header %q does not contain websocket", resp.Header.Get("Upgrade"))
return fmt.Errorf("websocket protocol violation: Upgrade header %q does not contain websocket", resp.Header.Get("Upgrade"))
}

if resp.Header.Get("Sec-WebSocket-Accept") != secWebSocketAccept(r.Header.Get("Sec-WebSocket-Key")) {
return xerrors.Errorf("websocket protocol violation: invalid Sec-WebSocket-Accept %q, key %q",
return fmt.Errorf("websocket protocol violation: invalid Sec-WebSocket-Accept %q, key %q",
resp.Header.Get("Sec-WebSocket-Accept"),
r.Header.Get("Sec-WebSocket-Key"),
)
Expand Down
7 changes: 3 additions & 4 deletions example_echo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"time"

"golang.org/x/time/rate"
"golang.org/x/xerrors"

"nhooyr.io/websocket"
"nhooyr.io/websocket/wsjson"
Expand Down Expand Up @@ -78,14 +77,14 @@ func echoServer(w http.ResponseWriter, r *http.Request) error {

if c.Subprotocol() != "echo" {
c.Close(websocket.StatusPolicyViolation, "client must speak the echo subprotocol")
return xerrors.Errorf("client does not speak echo sub protocol")
return fmt.Errorf("client does not speak echo sub protocol")
}

l := rate.NewLimiter(rate.Every(time.Millisecond*100), 10)
for {
err = echo(r.Context(), c, l)
if err != nil {
return xerrors.Errorf("failed to echo with %v: %w", r.RemoteAddr, err)
return fmt.Errorf("failed to echo with %v: %w", r.RemoteAddr, err)
}
}
}
Expand Down Expand Up @@ -114,7 +113,7 @@ func echo(ctx context.Context, c *websocket.Conn, l *rate.Limiter) error {

_, err = io.Copy(w, r)
if err != nil {
return xerrors.Errorf("failed to io.Copy: %w", err)
return fmt.Errorf("failed to io.Copy: %w", err)
}

err = w.Close()
Expand Down
5 changes: 2 additions & 3 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ package websocket_test

import (
"context"
"errors"
"log"
"net/http"
"time"

"golang.org/x/xerrors"

"nhooyr.io/websocket"
"nhooyr.io/websocket/wsjson"
)
Expand Down Expand Up @@ -76,7 +75,7 @@ func ExampleCloseError() {

_, _, err = c.Reader(ctx)
var cerr websocket.CloseError
if !xerrors.As(err, &cerr) || cerr.Code != websocket.StatusNormalClosure {
if !errors.As(err, &cerr) || cerr.Code != websocket.StatusNormalClosure {
log.Fatalf("expected to be disconnected with StatusNormalClosure but got: %+v", err)
return
}
Expand Down
5 changes: 2 additions & 3 deletions export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ package websocket
import (
"bufio"
"context"

"golang.org/x/xerrors"
"fmt"
)

type (
Expand Down Expand Up @@ -65,7 +64,7 @@ func (c *Conn) WriteHeader(ctx context.Context, h Header) error {
})
_, err := c.bw.Write(headerBytes)
if err != nil {
return xerrors.Errorf("failed to write header: %w", err)
return fmt.Errorf("failed to write header: %w", err)
}
if h.Fin {
err = c.Flush()
Expand Down
5 changes: 1 addition & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module nhooyr.io/websocket

go 1.12
go 1.13

require (
github.com/fatih/color v1.7.0 // indirect
Expand All @@ -18,12 +18,9 @@ require (
go.uber.org/atomic v1.4.0 // indirect
go.uber.org/multierr v1.1.0
golang.org/x/lint v0.0.0-20190409202823-959b441ac422
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297
golang.org/x/sys v0.0.0-20190830142957-1e83adbbebd0 // indirect
golang.org/x/text v0.3.2 // indirect
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
golang.org/x/tools v0.0.0-20190830223141-573d9926052a
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gotest.tools v2.2.0+incompatible // indirect
gotest.tools/gotestsum v0.3.6-0.20190825182939-fc6cb5870c52
Expand Down
Loading