WebSocket for fasthttp

websocket

WebSocket library for fasthttp and net/http.

Checkout examples to inspire yourself.

Install

go get github.com/dgrr/websocket

Why another WebSocket package?

Other WebSocket packages DON'T allow concurrent Read/Write operations on servers and they do not provide low level access to WebSocket packet crafting. Those WebSocket packages try to emulate the Golang API by implementing io.Reader and io.Writer interfaces on their connections. io.Writer might be a good idea to use it, but no io.Reader, given that WebSocket is an async protocol by nature (all protocols are (?)).

Sometimes, WebSocket servers are just cumbersome when we want to handle a lot of clients in an async manner. For example, in other WebSocket packages to broadcast a message generated internally we'll need to do the following:

type MyWebSocketService struct {
    clients sync.Map
}

type BlockingConn struct {
	lck sync.Mutex
	c websocketPackage.Conn
}

func (ws *MyWebSocketService) Broadcaster() {
	for msg := range messageProducerChannel {
        ws.clients.Range(func(_, v interface{}) bool {
            c := v.(*BlockingConn)
            c.lck.Lock() // oh, we need to block, otherwise we can break the program
            err := c.Write(msg)
            c.lck.Unlock()
            
            if err != nil {
                // we have an error, what can we do? Log it?
            	// if the connection has been closed we'll receive that on
            	// the Read call, so the connection will close automatically.
            }
            
            return true
        })
    }
}

func (ws *MyWebSocketService) Handle(request, response) {
	c, err := websocketPackage.Upgrade(request, response)
	if err != nil {
		// then it's clearly an error! Report back
    }
    
    bc := &BlockingConn{
        c: c,
    }   
    
	ws.clients.Store(bc, struct{}{})
    
	// even though I just want to write, I need to block somehow
    for {
    	content, err := bc.Read()
    	if err != nil {
            // handle the error
            break
        }
    }
    
    ws.clients.Delete(bc)
}

First, we need to store every client upon connection, and whenever we want to send data we need to iterate over a list, and send the message. If while, writing we get an error, then we need to handle that client's error What if the writing operation is happening at the same time in 2 different coroutines? Then we need a sync.Mutex and block until we finish writing.

To solve most of those problems websocket uses channels and separated coroutines, one for reading and another one for writing. By following the sharing principle.

Do not communicate by sharing memory; instead, share memory by communicating.

Following the fasthttp philosophy this library tries to take as much advantage of the Golang's multi-threaded model as possible, while keeping your code concurrently safe.

To see an example of what this package CAN do that others DONT checkout the broadcast example.

Server

How can I launch a server?

It's quite easy. You only need to create a Server, set your callbacks by calling the Handle* methods and then specify your fasthttp handler as Server.Upgrade.

package main

import (
	"fmt"
	
	"github.com/valyala/fasthttp"
	"github.com/dgrr/websocket"
)

func main() {
	ws := websocket.Server{}
	ws.HandleData(OnMessage)
	
	fasthttp.ListenAndServe(":8080", ws.Upgrade)
}

func OnMessage(c *websocket.Conn, isBinary bool, data []byte) {
	fmt.Printf("Received data from %s: %s\n", c.RemoteAddr(), data)
}

How can I launch a server if I use net/http?

package main

import (
	"fmt"
	"net/http"
	
	"github.com/dgrr/websocket"
)

func main() {
	ws := websocket.Server{}
	ws.HandleData(OnMessage)
	
	http.HandleFunc("/", ws.NetUpgrade)
	http.ListenAndServe(":8080", nil)
}

func OnMessage(c *websocket.Conn, isBinary bool, data []byte) {
	fmt.Printf("Received data from %s: %s\n", c.RemoteAddr(), data)
}

How can I handle pings?

Pings are handle automatically by the library, but you can get the content of those pings setting the callback using HandlePing.

For example, let's try to get the round trip time to a client by using the PING frame. The website http2.gofiber.io uses this method to measure the round trip time displayed at the bottom of the webpage.

package main

import (
	"sync"
	"encoding/binary"
	"log"
	"time"

	"github.com/valyala/fasthttp"
	"github.com/dgrr/websocket"
)

// Struct to keep the clients connected
//
// it should be safe to access the clients concurrently from Open and Close.
type RTTMeasure struct {
	clients sync.Map
}

// just trigger the ping sender
func (rtt *RTTMeasure) Start() {
	time.AfterFunc(time.Second * 2, rtt.sendPings)
}

func (rtt *RTTMeasure) sendPings() {
	var data [8]byte

	binary.BigEndian.PutUint64(data[:], uint64(
		time.Now().UnixNano()),
	)

	rtt.clients.Range(func(_, v interface{}) bool {
		c := v.(*websocket.Conn)
		c.Ping(data[:])
		return true
	})

	rtt.Start()
}

// register a connection when it's open
func (rtt *RTTMeasure) RegisterConn(c *websocket.Conn) {
	rtt.clients.Store(c.ID(), c)
	log.Printf("Client %s connected\n", c.RemoteAddr())
}

// remove the connection when receiving the close
func (rtt *RTTMeasure) RemoveConn(c *websocket.Conn, err error) {
	rtt.clients.Delete(c.ID())
	log.Printf("Client %s disconnected\n", c.RemoteAddr())
}

func main() {
	rtt := RTTMeasure{}

	ws := websocket.Server{}
	ws.HandleOpen(rtt.RegisterConn)
	ws.HandleClose(rtt.RemoveConn)
	ws.HandlePong(OnPong)

	// schedule the timer
	rtt.Start()

	fasthttp.ListenAndServe(":8080", ws.Upgrade)
}

// handle the pong message
func OnPong(c *websocket.Conn, data []byte) {
	if len(data) == 8 {
		n := binary.BigEndian.Uint64(data)
		ts := time.Unix(0, int64(n))

		log.Printf("RTT with %s is %s\n", c.RemoteAddr(), time.Now().Sub(ts))
	}
}

websocket vs gorilla vs nhooyr vs gobwas

Features websocket Gorilla Nhooyr gowabs
Concurrent R/W Yes No No. Only writes No
Passes Autobahn Test Suite Mostly Yes Yes Mostly
Receive fragmented message Yes Yes Yes Yes
Send close message Yes Yes Yes Yes
Send pings and receive pongs Yes Yes Yes Yes
Get the type of a received data message Yes Yes Yes Yes
Compression Extensions No Experimental Yes No (?)
Read message using io.Reader No Yes No No (?)
Write message using io.WriteCloser Yes Yes No No (?)

Stress tests

The following stress test were performed without timeouts:

Executing tcpkali --ws -c 100 -m 'hello world!!13212312!' -r 10k localhost:8081 the tests shows the following:

Websocket:

Total data sent:     267.7 MiB (280678466 bytes)
Total data received: 229.5 MiB (240626600 bytes)
Bandwidth per channel: 4.167⇅ Mbps (520.9 kBps)
Aggregate bandwidth: 192.357↓, 224.375↑ Mbps
Packet rate estimate: 247050.1↓, 61842.9↑ (1↓, 1↑ TCP MSS/op)
Test duration: 10.0075 s.

Websocket for net/http:

Total data sent:     267.3 MiB (280320124 bytes)
Total data received: 228.3 MiB (239396374 bytes)
Bandwidth per channel: 4.156⇅ Mbps (519.5 kBps)
Aggregate bandwidth: 191.442↓, 224.168↑ Mbps
Packet rate estimate: 188107.1↓, 52240.7↑ (1↓, 1↑ TCP MSS/op)
Test duration: 10.0039 s.

Either for fasthttp and net/http should be quite close, the only difference is the way they both upgrade.

Gorilla:

Total data sent:     260.2 MiB (272886768 bytes)
Total data received: 109.3 MiB (114632982 bytes)
Bandwidth per channel: 3.097⇅ Mbps (387.1 kBps)
Aggregate bandwidth: 91.615↓, 218.092↑ Mbps
Packet rate estimate: 109755.3↓, 66807.4↑ (1↓, 1↑ TCP MSS/op)
Test duration: 10.01 s.

Nhooyr: (Don't know why is that low)

Total data sent:     224.3 MiB (235184096 bytes)
Total data received: 41.2 MiB (43209780 bytes)
Bandwidth per channel: 2.227⇅ Mbps (278.3 kBps)
Aggregate bandwidth: 34.559↓, 188.097↑ Mbps
Packet rate estimate: 88474.0↓, 55256.1↑ (1↓, 1↑ TCP MSS/op)
Test duration: 10.0027 s.

Gobwas:

Total data sent:     265.8 MiB (278718160 bytes)
Total data received: 117.8 MiB (123548959 bytes)
Bandwidth per channel: 3.218⇅ Mbps (402.2 kBps)
Aggregate bandwidth: 98.825↓, 222.942↑ Mbps
Packet rate estimate: 148231.6↓, 72106.1↑ (1↓, 1↑ TCP MSS/op)
Test duration: 10.0015 s.

The source files are in this folder.

Owner
Darío
Trading Engineer programming in C++ and Go.
Darío
Similar Resources

: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

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

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

A tiny command line websocket client written in Go

wsc A simplistic tool for sending and receiving websocket messages from a command line. Mainly useful to test websocket servers. Getting started: $ go

Jan 12, 2022

An online multiplayer, websocket based, interpretation of the Tic Tac Toe minigame from "Machinarium" :D

An online multiplayer, websocket based, interpretation of the Tic Tac Toe minigame from

Tik Tak Toe An interpretation of the tic tac toe minigame from Amanita Design's Machinarium, multiplayer, online, as a website. Here's a screenshot of

Aug 31, 2022

A websocket powered discord wrapper for Sugaroid written in golang

sg-discord A thin discord wrapper built on top of Sugaroid Websocket implementation. Build go build . Run export DISCORD_BOT_TOKEN="supersecrettoken"

Dec 30, 2021
Comments
  • Always close connection

    Always close connection

    When using a "Normal" close status (ie, 1000), the close handler is not being called because Conn.closer was never closed. This commit fixes that.

  • Fix 'cannot create context from nil parent' panic

    Fix 'cannot create context from nil parent' panic

    An issue introduced with the v0.0.10 to support standard context.Context for net/http does not use an initialized context, causing the context package to panic with cannot create context from nil parent.

    This commit changes nctx so that it is initialized with the context.Background() value.

  • Production-ready? (+ a use case question)

    Production-ready? (+ a use case question)

    Would you say this library is currently suitable for production?

    Also, feel free to skip this question since it's pretty broad:

    I'm working on a websocket-based web game where I want to minimize latency and maximize synchronicity between all players. To the extent possible, I want the server to broadcast messages to every connected client at the same time, and have it quickly respond to player actions. The plan is to use fasthttp + this library.

    When a player client makes an action, it sends a message to the websocket server which verifies the message and broadcasts it to every other client. I understand due to connection latency differences it'll never be 100% real-time, but I want the player experience to feel as real-time, synchronized, and responsive as possible.

    My question is if there are any tips or techniques I should keep in mind to optimize this while using this library. I was planning to basically do what's shown in https://github.com/dgrr/websocket/blob/master/examples/broadcast/main.go#L38, plus a read loop, but I'm wondering if there's anything else I should look into as well.

    For example, do you think there'd be any benefit to using the lower-level frame APIs? Are there any other special considerations for implementing a read loop with a lot of concurrent connections + broadcasting each received message to all clients?

    Thanks.

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
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
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
Tiny WebSocket library for Go.

RFC6455 WebSocket implementation in Go.

Dec 28, 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
Minimal and idiomatic WebSocket library for Go

websocket websocket is a minimal and idiomatic WebSocket library for Go. Install go get nhooyr.io/websocket Highlights Minimal and idiomatic API First

Dec 31, 2022