good morning!!!!

Skip to content
Snippets Groups Projects
Unverified Commit 7ad15141 authored by Anmol Sethi's avatar Anmol Sethi
Browse files

Update README.md comparison

parent 120911b5
Branches
Tags
No related merge requests found
...@@ -16,17 +16,17 @@ go get nhooyr.io/websocket ...@@ -16,17 +16,17 @@ go get nhooyr.io/websocket
## Features ## Features
- Minimal and idiomatic API - Minimal and idiomatic API
- Tiny codebase at 2200 lines
- First class [context.Context](https://blog.golang.org/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) - Thorough tests, fully passes the [autobahn-testsuite](https://github.com/crossbario/autobahn-testsuite)
- [Zero dependencies](https://godoc.org/nhooyr.io/websocket?imports) - [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 - 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
- Zero alloc reads and writes - Zero alloc reads and writes
- Concurrent writes out of the box - Concurrent writes
- [Complete Wasm](https://godoc.org/nhooyr.io/websocket#hdr-Wasm) support - WebSocket [Close handshake](https://godoc.org/nhooyr.io/websocket#Conn.Close)
- [Close handshake](https://godoc.org/nhooyr.io/websocket#Conn.Close) - [net.Conn](https://godoc.org/nhooyr.io/websocket#NetConn) wrapper
- Full support of [RFC 7692](https://tools.ietf.org/html/rfc7692) permessage-deflate compression extension - WebSocket [Pings](https://godoc.org/nhooyr.io/websocket#Conn.Ping)
- [RFC 7692](https://tools.ietf.org/html/rfc7692) permessage-deflate compression
- [Wasm](https://godoc.org/nhooyr.io/websocket#hdr-Wasm)
## Roadmap ## Roadmap
...@@ -34,11 +34,7 @@ go get nhooyr.io/websocket ...@@ -34,11 +34,7 @@ go get nhooyr.io/websocket
## Examples ## Examples
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). For a production quality example that demonstrates the full API, see the [echo example](https://godoc.org/nhooyr.io/websocket#example-package--Echo).
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).
There is also [websocket.CloseStatus](https://godoc.org/nhooyr.io/websocket#CloseStatus) to quickly grab the close status code out of a [websocket.CloseError](https://godoc.org/nhooyr.io/websocket#CloseError).
See the [CloseStatus godoc example](https://godoc.org/nhooyr.io/websocket#example-CloseStatus).
### Server ### Server
...@@ -87,83 +83,45 @@ c.Close(websocket.StatusNormalClosure, "") ...@@ -87,83 +83,45 @@ c.Close(websocket.StatusNormalClosure, "")
## Comparison ## Comparison
Before the comparison, I want to point out that gorilla/websocket was extremely useful in implementing the ### [gorilla/websocket](https://github.com/gorilla/websocket)
WebSocket protocol correctly so _big thanks_ to its authors. In particular, I made sure to go through the
issue tracker of gorilla/websocket to ensure I implemented details correctly and understood how people were
using WebSockets in production.
### gorilla/websocket
https://github.com/gorilla/websocket
The implementation of gorilla/websocket is 6 years old. As such, it is
widely used and very mature compared to nhooyr.io/websocket.
On the other hand, it has grown organically and now there are too many ways to do
the same thing. Compare the godoc of
[nhooyr/websocket](https://godoc.org/nhooyr.io/websocket) with
[gorilla/websocket](https://godoc.org/github.com/gorilla/websocket) side by side.
The API for nhooyr.io/websocket has been designed such that there is only one way to do things.
This makes it easy to use correctly. Not only is the API simpler, the implementation is
only 2200 lines whereas gorilla/websocket is at 3500 lines. That's more code to maintain,
more code to test, more code to document and more surface area for bugs.
Moreover, nhooyr.io/websocket supports newer Go idioms such as context.Context.
It also uses net/http's Client and ResponseWriter directly for WebSocket handshakes.
gorilla/websocket writes its handshakes to the underlying net.Conn.
Thus it has to reinvent hooks for TLS and proxies and prevents easy support of HTTP/2.
Some more advantages of nhooyr.io/websocket are that it supports concurrent writes and
makes it very easy to close the connection with a status code and reason. In fact,
nhooyr.io/websocket even implements the complete WebSocket close handshake for you whereas
with gorilla/websocket you have to perform it manually. See [gorilla/websocket#448](https://github.com/gorilla/websocket/issues/448).
The ping API is also nicer. gorilla/websocket requires registering a pong handler on the Conn
which results in awkward control flow. With nhooyr.io/websocket you use the Ping method on the Conn
that sends a ping and also waits for the pong.
Additionally, nhooyr.io/websocket can compile to [Wasm](https://godoc.org/nhooyr.io/websocket#hdr-Wasm) for the browser.
In terms of performance, the differences mostly depend on your application code. nhooyr.io/websocket
reuses message buffers out of the box if you use the wsjson and wspb subpackages.
As mentioned above, nhooyr.io/websocket also supports concurrent writers.
The WebSocket masking algorithm used by this package is [1.75x](https://github.com/nhooyr/websocket/releases/tag/v1.7.4)
faster than gorilla/websocket while using only pure safe Go.
The [permessage-deflate compression extension](https://tools.ietf.org/html/rfc7692) is fully supported by this library Advantages of nhooyr.io/websocket:
whereas gorilla only supports no context takeover mode. See our godoc for the differences. This will make a big - Minimal and idiomatic API
difference on bandwidth used in most use cases. - 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
The only performance con to nhooyr.io/websocket is that it uses a goroutine to support - Zero alloc reads and writes ([gorilla/websocket#535](https://github.com/gorilla/websocket/issues/535))
cancellation with context.Context. This costs 2 KB of memory which is cheap compared to - Full [context.Context](https://blog.golang.org/context) support
the benefits. - Uses [net/http.Client](https://golang.org/pkg/net/http/#Client) for dialing
- Will enable easy HTTP/2 support in the future
### x/net/websocket - Gorilla writes directly to a net.Conn and so duplicates features from net/http.Client.
- Concurrent writes
https://godoc.org/golang.org/x/net/websocket - Close handshake ([gorilla/websocket#448](https://github.com/gorilla/websocket/issues/448))
- Idiomatic [ping](https://godoc.org/nhooyr.io/websocket#Conn.Ping) API
Unmaintained and the API does not reflect WebSocket semantics. Should never be used. - gorilla/websocket requires registering a pong callback and then sending a Ping
- Wasm ([gorilla/websocket#432](https://github.com/gorilla/websocket/issues/432))
See https://github.com/golang/go/issues/18152 - Transparent buffer reuse with [wsjson](https://godoc.org/nhooyr.io/websocket/wsjson) and [wspb](https://godoc.org/nhooyr.io/websocket/wspb) subpackages
- [1.75x](https://github.com/nhooyr/websocket/releases/tag/v1.7.4) faster WebSocket masking implementation in pure Go
### gobwas/ws - Gorilla's implementation depends on unsafe and is slower
- Full [permessage-deflate](https://tools.ietf.org/html/rfc7692) compression extension support
https://github.com/gobwas/ws - Gorilla only supports no context takeover mode
- [CloseRead](https://godoc.org/nhooyr.io/websocket#Conn.CloseRead) helper
This library has an extremely flexible API but that comes at the cost of usability - Actively maintained ([gorilla/websocket#370](https://github.com/gorilla/websocket/issues/370))
and clarity.
Advantages of gorilla/websocket:
Due to its flexibility, it can be used in a event driven style for performance. - Widely used and mature
Definitely check out his fantastic [blog post](https://medium.freecodecamp.org/million-websockets-and-go-cc58418460bb) about performant WebSocket servers.
### [x/net/websocket](https://godoc.org/golang.org/x/net/websocket)
If you want a library that gives you absolute control over everything, this is the library.
But for 99.9% of use cases, nhooyr.io/websocket will fit better as it is both easier and Deprecated. See ([golang/go/issues/18152](https://github.com/golang/go/issues/18152)).
faster for normal idiomatic Go. The masking implementation is [1.75x](https://github.com/nhooyr/websocket/releases/tag/v1.7.4)
faster, the compression extensions are fully supported and as much as possible is reused by default. The [net.Conn](https://godoc.org/nhooyr.io/websocket#NetConn) wrapper will ease in transitioning to nhooyr.io/websocket.
See the gorilla/websocket comparison for more performance details. ### [gobwas/ws](https://github.com/gobwas/ws)
This library has an extremely flexible API that allows it to be used in an unidiomatic event driven style
for performance. See the author's [blog post](https://medium.freecodecamp.org/million-websockets-and-go-cc58418460bb).
When writing idiomatic Go, nhooyr.io/websocket is a better choice as it will be faster and easier to use.
## Users ## Users
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"nhooyr.io/websocket/internal/errd"
"time" "time"
"nhooyr.io/websocket/internal/bpool" "nhooyr.io/websocket/internal/bpool"
...@@ -96,15 +97,13 @@ func CloseStatus(err error) StatusCode { ...@@ -96,15 +97,13 @@ func CloseStatus(err error) StatusCode {
// Close will unblock all goroutines interacting with the connection once // Close will unblock all goroutines interacting with the connection once
// complete. // complete.
func (c *Conn) Close(code StatusCode, reason string) error { func (c *Conn) Close(code StatusCode, reason string) error {
err := c.closeHandshake(code, reason) return c.closeHandshake(code, reason)
if err != nil {
return fmt.Errorf("failed to close WebSocket: %w", err)
}
return nil
} }
func (c *Conn) closeHandshake(code StatusCode, reason string) error { func (c *Conn) closeHandshake(code StatusCode, reason string) (err error) {
err := c.cw.sendClose(code, reason) defer errd.Wrap(&err, "failed to close WebSocket")
err = c.cw.sendClose(code, reason)
if err != nil { if err != nil {
return err return err
} }
...@@ -115,7 +114,7 @@ func (c *Conn) closeHandshake(code StatusCode, reason string) error { ...@@ -115,7 +114,7 @@ func (c *Conn) closeHandshake(code StatusCode, reason string) error {
func (cw *connWriter) error(code StatusCode, err error) { func (cw *connWriter) error(code StatusCode, err error) {
cw.c.setCloseErr(err) cw.c.setCloseErr(err)
cw.sendClose(code, err.Error()) cw.sendClose(code, err.Error())
cw.c.close(nil) cw.c.closeWithErr(nil)
} }
func (cw *connWriter) sendClose(code StatusCode, reason string) error { func (cw *connWriter) sendClose(code StatusCode, reason string) error {
...@@ -135,7 +134,7 @@ func (cw *connWriter) sendClose(code StatusCode, reason string) error { ...@@ -135,7 +134,7 @@ func (cw *connWriter) sendClose(code StatusCode, reason string) error {
} }
func (cr *connReader) waitClose() error { func (cr *connReader) waitClose() error {
defer cr.c.close(nil) defer cr.c.closeWithErr(nil)
return nil return nil
......
...@@ -33,11 +33,10 @@ const ( ...@@ -33,11 +33,10 @@ const (
// frames will not be handled. See the docs on Reader and CloseRead. // frames will not be handled. See the docs on Reader and CloseRead.
// //
// Be sure to call Close on the connection when you // Be sure to call Close on the connection when you
// are finished with it to release the associated resources. // are finished with it to release associated resources.
// //
// Every error from Read or Reader will cause the connection // On any error from any method, the connection is closed
// to be closed so you do not need to write your own error message. // with an appropriate reason.
// This applies to the Read methods in the wsjson/wspb subpackages as well.
type Conn struct { type Conn struct {
subprotocol string subprotocol string
rwc io.ReadWriteCloser rwc io.ReadWriteCloser
...@@ -69,11 +68,12 @@ type connConfig struct { ...@@ -69,11 +68,12 @@ type connConfig struct {
} }
func newConn(cfg connConfig) *Conn { func newConn(cfg connConfig) *Conn {
c := &Conn{} c := &Conn{
c.subprotocol = cfg.subprotocol subprotocol: cfg.subprotocol,
c.rwc = cfg.rwc rwc: cfg.rwc,
c.client = cfg.client client: cfg.client,
c.copts = cfg.copts copts: cfg.copts,
}
c.cr.init(c, cfg.br) c.cr.init(c, cfg.br)
c.cw.init(c, cfg.bw) c.cw.init(c, cfg.bw)
...@@ -82,7 +82,7 @@ func newConn(cfg connConfig) *Conn { ...@@ -82,7 +82,7 @@ func newConn(cfg connConfig) *Conn {
c.activePings = make(map[string]chan<- struct{}) c.activePings = make(map[string]chan<- struct{})
runtime.SetFinalizer(c, func(c *Conn) { runtime.SetFinalizer(c, func(c *Conn) {
c.close(errors.New("connection garbage collected")) c.closeWithErr(errors.New("connection garbage collected"))
}) })
go c.timeoutLoop() go c.timeoutLoop()
...@@ -96,7 +96,7 @@ func (c *Conn) Subprotocol() string { ...@@ -96,7 +96,7 @@ func (c *Conn) Subprotocol() string {
return c.subprotocol return c.subprotocol
} }
func (c *Conn) close(err error) { func (c *Conn) closeWithErr(err error) {
c.closeMu.Lock() c.closeMu.Lock()
defer c.closeMu.Unlock() defer c.closeMu.Unlock()
...@@ -135,7 +135,7 @@ func (c *Conn) timeoutLoop() { ...@@ -135,7 +135,7 @@ func (c *Conn) timeoutLoop() {
c.cw.error(StatusPolicyViolation, errors.New("timed out")) c.cw.error(StatusPolicyViolation, errors.New("timed out"))
return return
case <-writeCtx.Done(): case <-writeCtx.Done():
c.close(fmt.Errorf("write timed out: %w", writeCtx.Err())) c.closeWithErr(fmt.Errorf("write timed out: %w", writeCtx.Err()))
return return
} }
} }
...@@ -185,7 +185,7 @@ func (c *Conn) ping(ctx context.Context, p string) error { ...@@ -185,7 +185,7 @@ func (c *Conn) ping(ctx context.Context, p string) error {
return c.closeErr return c.closeErr
case <-ctx.Done(): case <-ctx.Done():
err := fmt.Errorf("failed to wait for pong: %w", ctx.Err()) err := fmt.Errorf("failed to wait for pong: %w", ctx.Err())
c.close(err) c.closeWithErr(err)
return err return err
case <-pong: case <-pong:
return nil return nil
......
...@@ -199,7 +199,7 @@ func (cr *connReader) frameHeader(ctx context.Context) (header, error) { ...@@ -199,7 +199,7 @@ func (cr *connReader) frameHeader(ctx context.Context) (header, error) {
case <-ctx.Done(): case <-ctx.Done():
return header{}, ctx.Err() return header{}, ctx.Err()
default: default:
cr.c.close(err) cr.c.closeWithErr(err)
return header{}, err return header{}, err
} }
} }
...@@ -229,7 +229,7 @@ func (cr *connReader) framePayload(ctx context.Context, p []byte) (int, error) { ...@@ -229,7 +229,7 @@ func (cr *connReader) framePayload(ctx context.Context, p []byte) (int, error) {
return n, ctx.Err() return n, ctx.Err()
default: default:
err = fmt.Errorf("failed to read frame payload: %w", err) err = fmt.Errorf("failed to read frame payload: %w", err)
cr.c.close(err) cr.c.closeWithErr(err)
return n, err return n, err
} }
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment