diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 4e3aec7f4e52a9c84da7bd1c741d52758cade4ac..0000000000000000000000000000000000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: Bug report -about: Report problems and unexpected behaviour -title: '' -labels: bug -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behaviour, ideally with source code: - -1. ... -2. ... -3. ... - -**Expected behaviour** -A clear and concise description of what you expected to happen. - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 5d424b505f22e83501f293cd2602ddac5abc33fe..0000000000000000000000000000000000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea -title: '' -labels: feature -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context about the feature request here. diff --git a/README.md b/README.md index 921e8485195a83ca3dbc882c867d44ea94a1cd79..db962907c2d7003d757478126e85fa7c189aecd0 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ websocket is a minimal and idiomatic WebSocket library for Go. -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 +Go 1.12 is required as it uses a new [feature](https://github.com/golang/go/issues/26937#issuecomment-415855861) in net/http to perform WebSocket handshakes. This library is not final and the API is subject to change. @@ -19,13 +19,11 @@ go get nhooyr.io/websocket ## Features -- Full support of the WebSocket protocol -- Zero dependencies outside of the stdlib -- Very minimal and carefully considered API -- context.Context is first class -- net/http is used for WebSocket dials and upgrades +- Minimal yet pragmatic API +- First class context.Context support - Thoroughly tested, fully passes the [autobahn-testsuite](https://github.com/crossbario/autobahn-testsuite) -- All returned errors include detailed context +- Concurrent writes +- Zero dependencies outside of the stdlib ## Roadmap @@ -97,45 +95,45 @@ c.Close(websocket.StatusNormalClosure, "") ## Design considerations -- Minimal API is easier to maintain and for others to learn +- Minimal API is easier to maintain and learn - Context based cancellation is more ergonomic and robust than setting deadlines -- No pings or pongs because TCP keep alives work fine for HTTP/1.1 and they do not make +- No ping support because TCP keep alives work fine for HTTP/1.1 and they do not make sense with HTTP/2 (see #1) - net.Conn is never exposed as WebSocket's over HTTP/2 will not have a net.Conn. -- Functional options make the API very clean and easy to extend +- Structures are nicer than functional options, see [google/go-cloud#908](https://github.com/google/go-cloud/issues/908#issuecomment-445034143) - Using net/http's Client for dialing means we do not have to reinvent dialing hooks - and configurations. Just pass in a custom net/http client if you want custom dialing. + and configurations like other WebSocket libraries ## Comparison While I believe nhooyr/websocket has a better API than existing libraries, both gorilla/websocket and gobwas/ws were extremely useful in implementing the -WebSocket protocol correctly so big thanks to the authors of both. In particular, +WebSocket protocol correctly so **big thanks** to the authors of both. In particular, I made sure to go through the issue tracker of gorilla/websocket to make sure -I implemented details correctly. +I implemented details correctly and understood how people were using the package +in production. ### gorilla/websocket https://github.com/gorilla/websocket -This package is the community standard but it is very old and over time -has accumulated cruft. There are many ways to do the same thing and the API -is not clear. Just compare the godoc of +This package is the community standard but it is 6 years old and over time +has accumulated cruft. There are many ways to do the same thing, usage is not clear +and there are some rough edges. Just compare the godoc of [nhooyr/websocket](godoc.org/github.com/nhooyr/websocket) side by side with [gorilla/websocket](godoc.org/github.com/gorilla/websocket). The API for nhooyr/websocket has been designed such that there is only one way to do things -which makes using it correctly and safely much easier. - -In terms of lines of code, this library is around 2000 whereas gorilla/websocket is -at 7000. So while the API for nhooyr/websocket is simpler, the implementation is also -significantly simpler and easier to test which reduces the surface are of bugs. +which makes it easy to use correctly. Furthermore, nhooyr/websocket has support for newer Go idioms such as context.Context and also uses net/http's Client and ResponseWriter directly for WebSocket handshakes. gorilla/websocket writes its handshakes directly to a net.Conn which means it has to reinvent hooks for TLS and proxying and prevents support of HTTP/2. +Another advantage of nhooyr/websocket is that it supports multiple concurrent writers out +of the box. + ### x/net/websocket https://godoc.org/golang.org/x/net/websocket @@ -149,12 +147,12 @@ See https://github.com/golang/go/issues/18152 https://github.com/gobwas/ws This library has an extremely flexible API but that comes at the cost of usability -and clarity. Its not clear what the best way to do anything is. +and clarity. This library is fantastic in terms of performance. The author put in significant effort to ensure its speed and I have applied as many of its optimizations as -I could into nhooyr/websocket. +I could into nhooyr/websocket. Definitely check out his fantastic [blog post](https://medium.freecodecamp.org/million-websockets-and-go-cc58418460bb) about performant WebSocket servers. If you want a library that gives you absolute control over everything, this is the library, -but for most users, the API provided by nhooyr/websocket will definitely fit better as it will -be just as performant but much easier to use correctly. +but for most users, the API provided by nhooyr/websocket will fit better as it is just as +performant but much easier to use correctly and idiomatic. diff --git a/doc.go b/doc.go index 22e6327378945db92ef16c2057a0fabdcebb90ab..246170a2a4401831c53134a7925f1a61f85eb90e 100644 --- a/doc.go +++ b/doc.go @@ -2,8 +2,12 @@ // // See https://tools.ietf.org/html/rfc6455 // -// The echo example is the best way to understand how to correctly use the library. +// Please see https://nhooyr.io/websocket for thorough overview docs and a +// comparison with existing implementations. +// +// Conn, Dial, and Accept are the main entrypoints into this package. Use Dial to dial +// a WebSocket server, Accept to accept a WebSocket client dial and then Conn to interact +// with the resulting WebSocket connections. // -// Please see https://nhooyr.io/websocket for detailed design docs and a comparison with existing -// libraries. +// The echo example is the best way to understand how to correctly use the library. package websocket diff --git a/docs/CONTRIBUTING.md b/docs/contributing.md similarity index 84% rename from docs/CONTRIBUTING.md rename to docs/contributing.md index b33e722d2767ee517d5be755c2b66991f1c05a5f..3f267ef2985f2ed4fe333a460057db556dcde8aa 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/contributing.md @@ -1,4 +1,10 @@ -# Contributing Guidelines +# Contributing + +## Issues + +Please be as descriptive as possible with your description. + +## Pull requests Please split up changes into several small descriptive commits. diff --git a/example_echo_test.go b/example_echo_test.go index a196affb7d94de346813fa19fc023943a4d79717..d2867a45431ab43777e58e10c9189495da918f5d 100644 --- a/example_echo_test.go +++ b/example_echo_test.go @@ -17,7 +17,13 @@ import ( "nhooyr.io/websocket" ) +// Example_echo starts a WebSocket echo server and +// then dials the server and sends 5 different messages +// and prints out the server's responses. func Example_echo() { + // First we listen on port 0, that means the OS will + // assign us a random free port. This is the listener + // the server will serve on and the client will connect to. l, err := net.Listen("tcp", "localhost:0") if err != nil { log.Fatalf("failed to listen: %v", err) @@ -37,6 +43,7 @@ func Example_echo() { } defer s.Close() + // This starts the echo server on the listener. go func() { err := s.Serve(l) if err != http.ErrServerClosed { @@ -44,6 +51,7 @@ func Example_echo() { } }() + // Now we dial the server and send the messages. err = client("ws://" + l.Addr().String()) if err != nil { log.Fatalf("client failed: %v", err) @@ -57,6 +65,9 @@ func Example_echo() { // {"i":4} } +// echoServer is the WebSocket echo server implementation. +// It ensures the client speaks the echo subprotocol and +// only allows one message every 100ms with a 10 message burst. func echoServer(w http.ResponseWriter, r *http.Request) error { c, err := websocket.Accept(w, r, websocket.AcceptOptions{ Subprotocols: []string{"echo"}, @@ -82,6 +93,10 @@ func echoServer(w http.ResponseWriter, r *http.Request) error { } } +// echo reads from the websocket connection and then writes +// the received message back to it. +// It only waits 1 minute to read and write the message and +// limits the received message to 32768 bytes. func echo(ctx context.Context, c *websocket.Conn, l *rate.Limiter) error { ctx, cancel := context.WithTimeout(ctx, time.Minute) defer cancel() @@ -111,6 +126,9 @@ func echo(ctx context.Context, c *websocket.Conn, l *rate.Limiter) error { return err } +// client dials the WebSocket echo server at the given url. +// It then sends it 5 different messages and echo's the server's +// response to each. func client(url string) error { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() diff --git a/websocket.go b/websocket.go index 0c3c3eef9f68e15ea1dbf14d6d3444505495c43d..21c4ef3f5130a9a6fa4d74f5c15080d94e8345c8 100644 --- a/websocket.go +++ b/websocket.go @@ -429,6 +429,8 @@ func (c *Conn) writeControl(ctx context.Context, opcode opcode, p []byte) error // a WebSocket message of type dataType to the connection. // Ensure you close the writer once you have written the entire message. // Concurrent calls to Writer are ok. +// Writer will block if there is another goroutine with an open writer +// until writer is closed. func (c *Conn) Writer(ctx context.Context, typ MessageType) (io.WriteCloser, error) { select { case <-c.closed: