Minimal and idiomatic WebSocket library for Go

websocket

godoc coverage

websocket is a minimal and idiomatic WebSocket library for Go.

Install

go get nhooyr.io/websocket

Highlights

Roadmap

  • HTTP/2 #4

Examples

For a production quality example that demonstrates the complete API, see the echo example.

For a full stack example, see the chat example.

Server

http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
	c, err := websocket.Accept(w, r, nil)
	if err != nil {
		// ...
	}
	defer c.Close(websocket.StatusInternalError, "the sky is falling")

	ctx, cancel := context.WithTimeout(r.Context(), time.Second*10)
	defer cancel()

	var v interface{}
	err = wsjson.Read(ctx, c, &v)
	if err != nil {
		// ...
	}

	log.Printf("received: %v", v)

	c.Close(websocket.StatusNormalClosure, "")
})

Client

ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()

c, _, err := websocket.Dial(ctx, "ws://localhost:8080", nil)
if err != nil {
	// ...
}
defer c.Close(websocket.StatusInternalError, "the sky is falling")

err = wsjson.Write(ctx, c, "hi")
if err != nil {
	// ...
}

c.Close(websocket.StatusNormalClosure, "")

Comparison

gorilla/websocket

Advantages of gorilla/websocket:

Advantages of nhooyr.io/websocket:

golang.org/x/net/websocket

golang.org/x/net/websocket is deprecated. See golang/go/issues/18152.

The net.Conn can help in transitioning to nhooyr.io/websocket.

gobwas/ws

gobwas/ws has an extremely flexible API that allows it to be used in an event driven style for performance. See the author's blog post.

However when writing idiomatic Go, nhooyr.io/websocket will be faster and easier to use.

Owner
Anmol Sethi
on a sabbatical, back soon :)
Anmol Sethi
Comments
  • Gin compatabilty

    Gin compatabilty

    I'm trying to integrate this into my existing app which is using the Gin framework, but I'm unable to establish a connection from the browser. Any ideas how I can get this working?

    Server

    package main
    
    import (
    	"context"
    	"log"
    	"net/http"
    	"time"
    
    	"github.com/gin-gonic/gin"
    	"nhooyr.io/websocket"
    	"nhooyr.io/websocket/wsjson"
    )
    
    func main() {
    	router := gin.New()
    
    	router.GET("/ws", handler)
    
    	router.Run(":7000")
    }
    
    func handler(c *gin.Context) {
    	log.Println("upgrading")
    	conn, wsErr := websocket.Accept(c.Writer, c.Request, &websocket.AcceptOptions{
    		InsecureSkipVerify: true,
    	})
    
    	if wsErr != nil {
    		c.AbortWithError(http.StatusInternalServerError, wsErr)
    		return
    	}
    
    	defer conn.Close(websocket.StatusInternalError, "Closed unexepetedly")
    
    	log.Println("ready")
    
    	ctx, cancel := context.WithTimeout(c.Request.Context(), time.Minute)
    	defer cancel()
    	ctx = conn.CloseRead(ctx)
    
    	tick := time.NewTicker(time.Second * 5)
    	defer tick.Stop()
    
    	for {
    		select {
    		case <-ctx.Done():
    			log.Println("done")
    			conn.Close(websocket.StatusNormalClosure, "")
    			return
    		case <-tick.C:
    			writeErr := wsjson.Write(ctx, conn, map[string]interface{}{
    				"msg": "hello",
    			})
    			if writeErr != nil {
    				log.Println(writeErr)
    				return
    			}
    		}
    	}
    }
    

    Client

    const ws = new WebSocket('ws://localhost:7000/ws');
    ws.onopen = console.log;
    ws.onmessage = console.log;
    

    JS Error WebSocket connection to 'ws://localhost:7000/ws' failed: Error during WebSocket handshake: net::ERR_INVALID_HTTP_RESPONSE

    Server logs

    [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
     - using env:   export GIN_MODE=release
     - using code:  gin.SetMode(gin.ReleaseMode)
    
    [GIN-debug] GET    /ws                       --> main.handler (1 handlers)
    [GIN-debug] Listening and serving HTTP on :7000
    2019/10/25 15:17:49 upgrading
    2019/10/25 15:17:49 ready
    2019/10/25 15:17:54 done
    
  • Weird behaviour when channelling go-capnproto2 over websocket

    Weird behaviour when channelling go-capnproto2 over websocket

    My setup is a little convoluted. I use capnproto rpc over websockets (to simplify deployment) between my programs. However, the websocket server keeps failing to read a header somehow and crashes.

    rpc: writing return: failed to write msg: websocket closed: failed to read header: EOF
    

    and on the Dialer side:

    rpc: writing finish: failed to write msg: context canceled
    

    Sometimes both ends cancel their contexts at the same time. I'm not sure why.

  • Improve CloseError API

    Improve CloseError API

    I think the current API is a little obnoxious since it forces you to use xerrors.As to get the close error code which is very annoying.

    Also people forget to use xerrors.As, see https://github.com/sumorf/deribit-api/blob/6b581052e02d0808bed0bdc7a04f3dc4582e70d3/stream.go#L30

    @sumorf

    I don't like the ceremony involved with xerrors.As so I'd like to provide some sort of convenience API to get the error code.

  • Allow single frame writes

    Allow single frame writes

    See #57

    Some websocket implementations cannot handle fragmented messages and the current API only allows writing fragmented messages because you write all your data first to a writer and then call close which writes a continuation fin frame.

    It may also be good for performance.

    I'm not going to expose an API for this right now, opening this issue to see how badly its wanted.

  • Add chat example

    Add chat example

    Hello @nhooyr, maybe you could add chat example something like https://github.com/gorilla/websocket/tree/master/examples/chat would be easier to compare both implementations and also would serve as documentation.

  • Documentation should note that package ignores protection against untrusted client

    Documentation should note that package ignores protection against untrusted client

    The client does not mask sent messages or verify the the Sec-WebSocket-Accept header. Because these features are required by the protocol and related to security, the documentation should note the omission of the features.

    It's unlikely that the package will be used in an application running untrusted code, but the package might be used in a websocket proxy that forwards requests from untrusted code.

  • forceLock hanging in v1.8.3 and causing a goroutine leak

    forceLock hanging in v1.8.3 and causing a goroutine leak

    I've only just noticed this and have only been able to confirm it was introduced in v1.8.3 I have not been able to determine if it's a mistake in my using of the library or even make a good test case yet.

    upgrading to v1.8.3 has introduced an issue where I end up with a bunch of goroutines that seem to be blocking in forceLock in conn_notjs.go. The following is my pprof output

    48 @ 0x436c00 0x4050bd 0x404e85 0x9f2324 0x9f2302 0x9f7ed7 0x466611
    #	0x9f2323	nhooyr.io/websocket.(*mu).forceLock+0x43	nhooyr.io/[email protected]/conn_notjs.go:234
    #	0x9f2301	nhooyr.io/websocket.(*msgReader).close+0x21	nhooyr.io/[email protected]/read.go:112
    #	0x9f7ed6	nhooyr.io/websocket.(*Conn).close.func1+0x46	nhooyr.io/[email protected]/conn_notjs.go:144
    
  • NetConn adapter

    NetConn adapter

    Was going through some random reading and I noticed this is an issue that appeared multiple times on the gorilla/websocket tracker:

    • https://github.com/gorilla/websocket/issues/282#issuecomment-327764790
    • https://github.com/gorilla/websocket/issues/441

    People using nhooyr/websocket also have ran into this: #80

    With my solution in #87, this is relatively simple to implement.

    I'm thinking websocket.NetConn(c *websocket.Conn) net.Conn

  • Compile for WebAssembly

    Compile for WebAssembly

    @albrow I saw you in issue https://github.com/gorilla/websocket/issues/432#issuecomment-518355272

    If you'd like to contribute the necessary improvements to this library, I'd be more than happy to help, review and merge your changes.

  • Read after write fails (Chrome DevTools Protocol server)

    Read after write fails (Chrome DevTools Protocol server)

    Using this simple program:

    package main
    
    import (
    	"context"
    	"encoding/json"
    	"errors"
    	"flag"
    	"log"
    	"time"
    
    	"nhooyr.io/websocket"
    )
    
    var flagRemote = flag.String("r", "", "remote url")
    
    func main() {
    	flag.Parse()
    	if err := run(); err != nil {
    		log.Fatalf("error: %v", err)
    	}
    }
    
    func run() error {
    	if *flagRemote == "" {
    		return errors.New("must provide remote url via -r")
    	}
    
    	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
    	defer cancel()
    
    	c, _, err := websocket.Dial(ctx, *flagRemote)
    	if err != nil {
    		return err
    	}
    	defer c.Close(websocket.StatusInternalError, "")
    
    	// write initial message
    	w, err := c.Write(ctx, websocket.MessageText)
    	if err != nil {
    		return err
    	}
    	if _, err = w.Write([]byte(`{"id":1,"method":"Target.getTargets","params":{}}`)); err != nil {
    		return err
    	}
    	if err = w.Close(); err != nil {
    		return err
    	}
    	log.Printf("wrote Target.getTargets message")
    
    	// read response
    	jc := websocket.JSONConn{
    		Conn: c,
    	}
    	var v interface{}
    	err = jc.Read(ctx, &v)
    	if err != nil {
    		log.Fatalf("failed to read json: %v", err)
    	}
    	buf, err := json.Marshal(v)
    	if err != nil {
    		return err
    	}
    	log.Printf("received %s", string(buf))
    
    	return c.Close(websocket.StatusNormalClosure, "")
    }
    

    And using it with Google Chrome's DevTools Protocol, on sending a simple message via this command:

    Starting Chrome:

    ken@ken-desktop:~/src/go/src/github.com/kenshaw/wstest$ google-chrome --headless --remote-debugging-port=0 --disable-gpu
    
    DevTools listening on ws://127.0.0.1:32831/devtools/browser/4f7e048b-1c84-4f05-a7fe-39fd624fb07e
    

    And then running the simple Go test program above:

    ken@ken-desktop:~/src/go/src/github.com/kenshaw/wstest$ go build && ./wstest -r 'ws://127.0.0.1:32831/devtools/browser/4f7e048b-1c84-4f05-a7fe-39fd624fb07e'
    2019/04/20 20:10:49 wrote Target.getTargets message
    2019/04/20 20:10:49 failed to read json: failed to read json: websocket: failed to read message: websocket: connection broken: failed to read header: EOF
    ken@ken-desktop:~/src/go/src/github.com/kenshaw/wstest$
    

    Expected output: the response from Chrome, looking something like the following:

    {"id":1,"result":{"targetInfos":[{"targetId":"D18C239D31BAB5964945BD536DD9C987","type":"page","title":"","url":"about:blank","attached":false,"browserContextId":"FECF894940A790B796F6A86E836A8242"}]}}
    

    Note: both the gobwas/ws and gorilla/websocket packages work with Chrome's DevTools Protocol.

    I've attempted to look through the nhooyr.io/websocket package to determine what the cause is, but have not been able to track down what the issue is. My guess is that it's a non-standard header or some such that Chrome is returning.

    Perhaps I'm not using the package properly, if so, I apologize in advance, however I believe I correctly followed the examples provided in the package. Appreciate any pointers to doing this properly -- I'm also willing to help debug this issue. Thanks in advance!

  • Confirm AcceptOrigins API

    Confirm AcceptOrigins API

    I think it's a little awkward and inflexible. Maybe its better to have devs verify the origin themselves if its not equal to r.Host and then pass an option like websocket.AcceptInsecureOrigin()

  • Consider updating declared Go version in go.mod

    Consider updating declared Go version in go.mod

    The go.mod file declares go 1.13 right now. Module graph pruning was added in Go 1.17, meaning that other modules can import your module and only pick up the transitive dependencies that are actually reachable based on how they use your code. Right now, importing websocket means importing, for example, a protobuf package because the declared go version predates go 1.17.

    The tidiest dependency graph will come from using go 1.18, as:

    The [go.sum file](https://tip.golang.org/ref/mod#go-sum-files) recorded by [go mod tidy](https://tip.golang.org/ref/mod#go-mod-tidy) for a module by default includes checksums needed by the Go version one below the version specified in its [go directive](https://tip.golang.org/ref/mod#go-mod-file-go). So a go 1.17 module includes checksums needed for the full module graph loaded by Go 1.16, but a go 1.18 module will include only the checksums needed for the pruned module graph loaded by Go 1.17. The -compat flag can be used to override the default version (for example, to prune the go.sum file more aggressively in a go 1.17 module).
    

    Go 1.18 is also the oldest Go release still supported by the Go team, so it's a reasonable choice of language version to declare.

  • failed to get reader: WebSocket closed

    failed to get reader: WebSocket closed

    i try to connect to the websocket server using socket.io in browser, the server code is echo example, the frontend code is as follows:

    <script src="https://cdn.bootcdn.net/ajax/libs/socket.io/4.5.4/socket.io.min.js"></script>
    <script>
      const socket = io("ws://localhost:8008", {
        transports: ["websocket", "polling"], // use WebSocket first, if available
        protocols: ["echo"],
        reconnectionDelayMax: 10000,
        path: "/",
        // auth: {
        //   token: "20221202"
        // },
        query: {
        }
      });
    
      socket.on("connect", () => {
        console.log("connected: ", socket.connected);
        console.log("id: ", socket.id);
      });
    </script>
    

    but i got the error:

    failed to echo with 127.0.0.1:2509: failed to get reader: WebSocket closed: read timed out: context deadline exceeded
    failed to echo with 127.0.0.1:2539: failed to get reader: context deadline exceeded
    
  • Example compiled on windows flags as malware on virustotal.

    Example compiled on windows flags as malware on virustotal.

    Just built the example on windows with go 1.19.3

    cd examples\echo
    go build ./
    

    ... and then uploaded the resulting echo.exe to virustotal.com and it gets multiple errors.

    https://www.virustotal.com/gui/file/dec851d0df6d845f3b47ffbcba7ee66a5144cb06f406616b1cce21d3067085a1/detection

    I first noticed it because my local eset antivirus blocked files that used this websocket module during compile

    PS D:\Code\kmpm\websocket\examples\echo> go build ./
    open C:\Users\peter\AppData\Local\Temp\go-build3248632192\b001\exe\a.out.exe: Access denied.
    

    Some examples of what virustotal thinks.

    | vendor | status | |------------------|-------------------------------------| | eset-nod32 | A Variant Of WinGo/Agent.KN | | McAffe | Artemis!BFA767614E38 | | Microsoft | Trojan:Win32/Wacatac.B!ml | | Fortinet | W32/Agent.KN!tr |

  • Update the chat example to publish messages on the websocket connection

    Update the chat example to publish messages on the websocket connection

    I think the chat example would be a lot more valuable if the messages were published via the websocket connection instead of as separate HTTP request. I'm trying to migrate from gorilla/websockets but having a hard time figuring out how to both read from and write to a client from different threads. If not publishing the messages themselves, maybe a "typing" indicator when a user is typing in the input? Anything that demonstrates concurrent full duplex communication over websockets really.

  • How to cleanly disconnect?

    How to cleanly disconnect?

    Problem: If I have an application that waits for data over the websocket, how do I cleanly shut down the websocket? Here, cleanly means:

    1. stop waiting for new data
    2. perform the closing handshake
    3. verify that these things happened with no errors

    Our first attempt at this calls (*Conn).Read(ctx) in a loop on one goroutine, and then when it was time to shut down, calls (*Conn).Close() from a different goroutine.

    However, this would often result in close errors reading the response packet. Investigating, I found that calling Close() concurrently with Read() from different goroutines exposed a race condition where Read() would read the frame header, and then Close() would read the packet payload as if it were a header, and throw an error.

    Would you consider this a bug? That is to say, are you meant to be able to call Read() and Close() from different goroutines concurrently?

    My next attempt cancels the Context passed to Read(), and then calls Close() from the same goroutine after Read() returns. There are a couple problems with this approach. Firstly, canceling the Read() context seems to trigger an opClose with status PolicyViolation. Ideally I'd be sending a normal status on disconnect. Secondly, Close() returns a couple different error messages that seem to depend on some races in the library, including "already wrote close" and "WebSocket closed", so it's really hard to verify that the close handshake completed correctly.

Encrypted-websocket-chat - Encrypted websocket chat using golang

Encrypted websocket chat First version written in python This version should be

Sep 15, 2022
Websocket-chat - A simple websocket chat application
Websocket-chat - A simple websocket chat application

WebSocket Chat App This is a simple chat app based on websockets. It allows user

Jan 25, 2022
Tiny WebSocket library for Go.

RFC6455 WebSocket implementation in Go.

Dec 28, 2022
A go library for consuming Binance Websocket Market Streams

binancestream A go library for consuming Binance Websocket Market Streams This library handles network failures by automatically reconnecting to the w

Aug 1, 2022
API that upgrades connection to use websocket. Contains server and client and testing how they communicate

Websocket Test API How to execute First run server using: make run-server. Then run many client instances with: make run-client. Then start typing in

Dec 25, 2021
A fast, well-tested and widely used WebSocket implementation for Go.

Gorilla WebSocket Gorilla WebSocket is a Go implementation of the WebSocket protocol. Documentation API Reference Chat example Command example Client

Jan 2, 2023
A modern, fast and scalable websocket framework with elegant API written in Go
A modern, fast and scalable websocket framework with elegant API written in Go

About neffos Neffos is a cross-platform real-time framework with expressive, elegant API written in Go. Neffos takes the pain out of development by ea

Dec 29, 2022
Websocket server. Get data from provider API, clean data and send to websoket, when it's changed.

Описание Сервис получает данные по киберспортивным матчам CS:GO от провайдера, структурирует, очищает от лишнего и отправляет всем активным вебсокет к

Apr 6, 2022
Go-distributed-websocket - Distributed Web Socket with Golang and Redis
Go-distributed-websocket - Distributed Web Socket with Golang and Redis

go-distributed-websocket Distributed Web Socket with Golang and Redis Dependenci

Oct 13, 2022
Turn any program that uses STDIN/STDOUT into a WebSocket server. Like inetd, but for WebSockets.

websocketd websocketd is a small command-line tool that will wrap an existing command-line interface program, and allow it to be accessed via a WebSoc

Dec 31, 2022
WebSocket Command Line Client written in Go

ws-cli WebSocket Command Line Client written in Go Installation go get github.com/kseo/ws-cli Usage $ ws-cli -url ws://echo.websocket.org connected (

Nov 12, 2021
proxy your traffic through CDN using websocket

go-cdn2proxy proxy your traffic through CDN using websocket what does it do example server client thanks what does it do you can use this as a library

Dec 7, 2022
Chat bots (& more) for Zoom by figuring out their websocket protocol
Chat bots (& more) for Zoom by figuring out their websocket protocol

zoomer - Bot library for Zoom meetings Good bot support is part of what makes Discord so nice to use. Unfortunately, the official Zoom API is basicall

Dec 14, 2022
Simple example for using Turbos Streams in Go with the Gorilla WebSocket toolkit.

Go Example for TurboStreams over WebSockets Simple example for using Turbos Streams in Go with the Gorilla WebSocket toolkit.

Dec 22, 2022
:notes: Minimalist websocket framework for Go
:notes: Minimalist websocket framework for Go

melody ?? Minimalist websocket framework for Go. Melody is websocket framework based on github.com/gorilla/websocket that abstracts away the tedious p

Dec 23, 2022
Terminal on browser via websocket

Terminal on browser via websocket. Supportted OS Linux Mac

Dec 27, 2022
run shell scripts by websocket with go lauguage
run shell scripts by websocket with go lauguage

go_shell_socket run shell scripts by websocket with go lauguage Usage pull project get gin and websocket with go get config config.json file build it

Mar 9, 2022
simpleChatInGo - This is a simple chat that i made for fun asnd learn more about websocket
simpleChatInGo - This is a simple chat that i made for fun asnd learn more about websocket

simpleChatInGo This is a simple chat that i made for fun asnd learn more about websocket deploy For deploy this you only need to run the command : $ d

Sep 21, 2022
gatews - Gate.io WebSocket SDK

gatews - Gate.io WebSocket SDK gatews provides new Gate.io WebSocket V4 implementations. It is intended to work along with gateapi-* series to provide

Dec 31, 2022