Categorygithub.com/xenking/fastws
modulepackage
1.2.0
Repository: https://github.com/xenking/fastws.git
Documentation: pkg.go.dev

# README

fastws

Build Status codecov

WebSocket library for fasthttp. And now for net/http too.

Checkout examples to inspire yourself.

Why another WebSocket package?

Other WebSocket packages does not allow concurrent Read/Write operations and does not provide low level access to WebSocket packet crafting.

Following the fasthttp philosophy this library tries to avoid extra-allocations while providing concurrent access to Read/Write operations and stable API to be used in production allowing low level access to the WebSocket frames.

To see an example of what this package CAN do that others DONT checkout this or this examples.

How it works? (Server)

Okay. It's easy. You have an Upgrader which is used to upgrade your connection. You must specify the Handler to handle the request.

If you just want a WebSocket connection and don't want to be a WebSocket expert you can just use Upgrade function passing the handler.

package main

import (
	"fmt"
	
	"github.com/valyala/fasthttp"
	"github.com/xenking/fastws"
)

func main() {
	fasthttp.ListenAndServe(":8080", fastws.Upgrade(wsHandler))
}

func wsHandler(conn *fastws.Conn) {
	fmt.Fprintf(conn, "Hello world")
}

After this point you can handle your awesome WebSocket connection. The connection is automatically closed by fastws when you exit your handler. YES! You are able to close your connection if you want to send a close message to the peer.

If you are looking for a low level usage of the library you can use the Frame structure to handle frame by frame in a WebSocket connection. Also you can use Conn.ReadFrame or Conn.NextFrame to read frame by frame from your peers.

package main

import (
	"fmt"

	"github.com/valyala/fasthttp"
	"github.com/xenking/fastws"
)

func main() {
	fasthttp.ListenAndServe(":8080", fastws.Upgrade(wsHandler))
}

func wsHandler(conn *fastws.Conn) {
	fmt.Fprintf(conn, "Hello world")

	fr, err := conn.NextFrame()
	if err != nil {
		panic(err)
	}

	fmt.Printf("Received: %s\n", fr.Payload())

	fastws.ReleaseFrame(fr)
}

All of the Conn functions are safe-concurrent. Ready to be used from different goroutines. If you want to deal with the frames you can either use Conn or your own net.Conn, but remember to manage concurrency when using a net.Conn

How it works? (Client)

Just call Dial.

package main

import (
	"fmt"

	"github.com/xenking/fastws"
)

func main() {
	conn, err := fastws.Dial("ws://localhost:8080/ws")
	if err != nil {
		fmt.Println(err)
	}
	conn.WriteString("Hello")
}

fastws vs gorilla vs nhooyr vs gobwas

FeaturesfastwsGorillaNhooyrgowabs
Concurrent R/WYesNoNo. Only writesNo
Passes Autobahn Test SuiteMostlyYesYesMostly
Receive fragmented messageYesYesYesYes
Send close messageYesYesYesYes
Send pings and receive pongsYesYesYesYes
Get the type of a received data messageYesYesYesYes
Compression ExtensionsOn developmentExperimentalYesNo (?)
Read message using io.ReaderNot plannedYesNoNo (?)
Write message using io.WriteCloserNot plannedYesNoNo (?)

Benchmarks: fastws vs gorilla vs nhooyr vs gobwas

Fastws:

$ go test -bench=Fast -benchmem -benchtime=10s
Benchmark1000FastClientsPer10Messages-8          225367248    52.6 ns/op       0 B/op   0 allocs/op
Benchmark1000FastClientsPer100Messages-8        1000000000     5.48 ns/op      0 B/op   0 allocs/op
Benchmark1000FastClientsPer1000Messages-8       1000000000     0.593 ns/op     0 B/op   0 allocs/op
Benchmark100FastMsgsPerConn-8                   1000000000     7.38 ns/op      0 B/op   0 allocs/op
Benchmark1000FastMsgsPerConn-8                  1000000000     0.743 ns/op     0 B/op   0 allocs/op
Benchmark10000FastMsgsPerConn-8                 1000000000     0.0895 ns/op    0 B/op   0 allocs/op
Benchmark100000FastMsgsPerConn-8                1000000000     0.0186 ns/op    0 B/op   0 allocs/op

Gorilla:

$ go test -bench=Gorilla -benchmem -benchtime=10s
Benchmark1000GorillaClientsPer10Messages-8       128621386    97.5 ns/op      86 B/op   1 allocs/op
Benchmark1000GorillaClientsPer100Messages-8     1000000000    11.0 ns/op       8 B/op   0 allocs/op
Benchmark1000GorillaClientsPer1000Messages-8    1000000000     1.12 ns/op      0 B/op   0 allocs/op
Benchmark100GorillaMsgsPerConn-8                 849490059    14.0 ns/op       8 B/op   0 allocs/op
Benchmark1000GorillaMsgsPerConn-8               1000000000     1.42 ns/op      0 B/op   0 allocs/op
Benchmark10000GorillaMsgsPerConn-8              1000000000     0.143 ns/op     0 B/op   0 allocs/op
Benchmark100000GorillaMsgsPerConn-8             1000000000     0.0252 ns/op    0 B/op   0 allocs/op

Nhooyr:

$ go test -bench=Nhooyr -benchmem -benchtime=10s
Benchmark1000NhooyrClientsPer10Messages-8        121254158   114 ns/op        87 B/op   1 allocs/op
Benchmark1000NhooyrClientsPer100Messages-8      1000000000    11.1 ns/op       8 B/op   0 allocs/op
Benchmark1000NhooyrClientsPer1000Messages-8     1000000000     1.19 ns/op      0 B/op   0 allocs/op
Benchmark100NhooyrMsgsPerConn-8                  845071632    15.1 ns/op       8 B/op   0 allocs/op
Benchmark1000NhooyrMsgsPerConn-8                1000000000     1.47 ns/op      0 B/op   0 allocs/op
Benchmark10000NhooyrMsgsPerConn-8               1000000000     0.157 ns/op     0 B/op   0 allocs/op
Benchmark100000NhooyrMsgsPerConn-8              1000000000     0.0251 ns/op    0 B/op   0 allocs/op

Gobwas:

$ go test -bench=Gobwas -benchmem -benchtime=10s
Benchmark1000GobwasClientsPer10Messages-8         98497042   106 ns/op        86 B/op   1 allocs/op
Benchmark1000GobwasClientsPer100Messages-8      1000000000    13.4 ns/op       8 B/op   0 allocs/op
Benchmark1000GobwasClientsPer1000Messages-8     1000000000     1.19 ns/op      0 B/op   0 allocs/op
Benchmark100GobwasMsgsPerConn-8                  833576667    14.6 ns/op       8 B/op   0 allocs/op
Benchmark1000GobwasMsgsPerConn-8                1000000000     1.46 ns/op      0 B/op   0 allocs/op
Benchmark10000GobwasMsgsPerConn-8               1000000000     0.156 ns/op     0 B/op   0 allocs/op
Benchmark100000GobwasMsgsPerConn-8              1000000000     0.0262 ns/op    0 B/op   0 allocs/op

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:

Fastws:

Total data sent:     267.4 MiB (280416485 bytes)
Total data received: 229.2 MiB (240330760 bytes)
Bandwidth per channel: 4.164⇅ Mbps (520.5 kBps)
Aggregate bandwidth: 192.172↓, 224.225↑ Mbps
Packet rate estimate: 153966.7↓, 47866.8↑ (1↓, 1↑ TCP MSS/op)
Test duration: 10.0048 s.

Gorilla:

Total data sent:     267.6 MiB (280594916 bytes)
Total data received: 165.8 MiB (173883303 bytes)
Bandwidth per channel: 3.632⇅ Mbps (454.0 kBps)
Aggregate bandwidth: 138.973↓, 224.260↑ Mbps
Packet rate estimate: 215158.9↓, 74635.5↑ (1↓, 1↑ TCP MSS/op)
Test duration: 10.0096 s.

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

Total data sent:     234.3 MiB (245645988 bytes)
Total data received: 67.7 MiB (70944682 bytes)
Bandwidth per channel: 2.532⇅ Mbps (316.5 kBps)
Aggregate bandwidth: 56.740↓, 196.461↑ Mbps
Packet rate estimate: 92483.9↓, 50538.6↑ (1↓, 1↑ TCP MSS/op)
Test duration: 10.0028 s

Gobwas:

Total data sent:     267.6 MiB (280591457 bytes)
Total data received: 169.5 MiB (177693000 bytes)
Bandwidth per channel: 3.664⇅ Mbps (458.0 kBps)
Aggregate bandwidth: 142.080↓, 224.356↑ Mbps
Packet rate estimate: 189499.0↓, 66535.5↑ (1↓, 1↑ TCP MSS/op)
Test duration: 10.0052 s.

The source files are in this folder.

# Packages

No description provided by the author

# Functions

AcquireFrame gets Frame from the global pool.
Client returns Conn using existing connection.
ClientWithHeaders returns a Conn using existing connection and sending personalized headers.
Dial establishes a websocket connection as client.
DialTLS establishes a websocket connection as client with the parsed tls.Config.
DialWithHeaders establishes a websocket connection as client sending a personalized request.
NetUpgrade returns a RequestHandler for net/http doing the upgrading process easier.
ReleaseFrame puts fr Frame into the global pool.
Upgrade returns a RequestHandler for fasthttp doing the upgrading process easier.
UpgradeAsClient will upgrade the connection as a client r can be nil.

# Constants

CodeBinary defines the binary code.
CodeClose defines the close code.
CodeContinuation defines the continuation code.
CodePing defines the ping code.
CodePong defines the pong code.
CodeText defines the text code.
DefaultPayloadSize defines the default payload size (when none was defined).
ModeBinary defines to use a binary mode.
ModeText defines to use a text mode.
StatuseExtensionsNeeded IDK.
StatusGoAway peer's error.
StatusNone is used to let the peer know nothing happened.
StatusNotAcceptable when a request is not acceptable.
StatusNotConsistent IDK.
StatusProtocolError problem with the peer's way to communicate.
StatusReserved when a reserved field have been used.
StatusTooBig payload bigger than expected.
StatusUnexpected IDK.
StatusViolation a violation of the protocol happened.

# Variables

EOF represents an io.EOF error.
ErrCannotUpgrade shows up when an error ocurred when upgrading a connection.

# Structs

Conn represents websocket connection handler.
Frame is the unit used to transfer message between endpoints using the websocket protocol.
NetUpgrader upgrades HTTP connection to a websocket connection if it's possible.
Upgrader upgrades HTTP connection to a websocket connection if it's possible.

# Type aliases

Code to send.
Mode is the mode in which the bytes are sended.
No description provided by the author
No description provided by the author
StatusCode is sent when closing a connection.
No description provided by the author