fastws

package module
v0.0.0-...-813729a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 2, 2023 License: MIT Imports: 21 Imported by: 0

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.

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

func wsHandler(conn *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.

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

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

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

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

  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.

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

fastws vs gorilla vs nhooyr vs gobwas

Features fastws 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 On development Experimental Yes No (?)
Read message using io.Reader Not planned Yes No No (?)
Write message using io.WriteCloser Not planned Yes No No (?)

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.

Documentation

Index

Constants

View Source
const (
	// StatusNone is used to let the peer know nothing happened.
	StatusNone StatusCode = 1000
	// StatusGoAway peer's error.
	StatusGoAway = 1001
	// StatusProtocolError problem with the peer's way to communicate.
	StatusProtocolError = 1002
	// StatusNotAcceptable when a request is not acceptable
	StatusNotAcceptable = 1003
	// StatusReserved when a reserved field have been used
	StatusReserved = 1004
	// StatusNotConsistent IDK
	StatusNotConsistent = 1007
	// StatusViolation a violation of the protocol happened
	StatusViolation = 1008
	// StatusTooBig payload bigger than expected
	StatusTooBig = 1009
	// StatuseExtensionsNeeded IDK
	StatuseExtensionsNeeded = 1010
	// StatusUnexpected IDK
	StatusUnexpected = 1011
)
View Source
const DefaultPayloadSize = 1 << 22

DefaultPayloadSize defines the default payload size (when none was defined).

Variables

View Source
var (
	// EOF represents an io.EOF error.
	EOF = io.EOF
)
View Source
var (
	// ErrCannotUpgrade shows up when an error ocurred when upgrading a connection.
	ErrCannotUpgrade = errors.New("cannot upgrade connection")
)

Functions

func NetUpgrade

func NetUpgrade(handler RequestHandler) func(http.ResponseWriter, *http.Request)

NetUpgrade returns a RequestHandler for net/http doing the upgrading process easier.

func ReleaseFrame

func ReleaseFrame(fr *Frame)

ReleaseFrame puts fr Frame into the global pool.

func Upgrade

func Upgrade(handler RequestHandler) func(ctx *fasthttp.RequestCtx)

Upgrade returns a RequestHandler for fasthttp doing the upgrading process easier.

func UpgradeAsClient

func UpgradeAsClient(c net.Conn, url string, r *fasthttp.Request) error

UpgradeAsClient will upgrade the connection as a client

r can be nil.

Types

type Code

type Code uint8

Code to send.

const (
	// CodeContinuation defines the continuation code
	CodeContinuation Code = 0x0
	// CodeText defines the text code
	CodeText Code = 0x1
	// CodeBinary defines the binary code
	CodeBinary Code = 0x2
	// CodeClose defines the close code
	CodeClose Code = 0x8
	// CodePing defines the ping code
	CodePing Code = 0x9
	// CodePong defines the pong code
	CodePong Code = 0xA
)

type Conn

type Conn struct {

	// Mode indicates Write default mode.
	Mode Mode

	// ReadTimeout ...
	ReadTimeout time.Duration

	// WriteTimeout ...
	WriteTimeout time.Duration

	// MaxPayloadSize prevents huge memory allocation.
	//
	// By default MaxPayloadSize is DefaultPayloadSize.
	MaxPayloadSize uint64
	// contains filtered or unexported fields
}

Conn represents websocket connection handler.

This handler is compatible with io.Reader, io.ReaderFrom, io.Writer, io.WriterTo

func Client

func Client(c net.Conn, url string) (*Conn, error)

Client returns Conn using existing connection.

url must be complete URL format i.e. http://localhost:8080/ws

func ClientWithHeaders

func ClientWithHeaders(c net.Conn, url string, req *fasthttp.Request) (*Conn, error)

ClientWithHeaders returns a Conn using existing connection and sending personalized headers.

func Dial

func Dial(url string) (*Conn, error)

Dial establishes a websocket connection as client.

url parameter must follow WebSocket URL format i.e. ws://host:port/path

func DialTLS

func DialTLS(url string, cnf *tls.Config) (*Conn, error)

DialTLS establishes a websocket connection as client with the parsed tls.Config. The config will be used if the URL is wss:// like.

func DialWithHeaders

func DialWithHeaders(url string, req *fasthttp.Request) (*Conn, error)

DialWithHeaders establishes a websocket connection as client sending a personalized request.

func DialWithHeadersAndCustomDialer

func DialWithHeadersAndCustomDialer(url string, req *fasthttp.Request, customDialer *net.Dialer) (*Conn, error)

func (*Conn) Close

func (conn *Conn) Close() error

Close closes the websocket connection.

func (*Conn) CloseString

func (conn *Conn) CloseString(b string) error

CloseString sends b as close reason and closes the descriptor.

When connection is handled by server the connection is closed automatically.

func (*Conn) LocalAddr

func (conn *Conn) LocalAddr() net.Addr

LocalAddr returns local address.

func (*Conn) NextFrame

func (conn *Conn) NextFrame() (fr *Frame, err error)

NextFrame reads next connection frame and returns if there were no error.

If NextFrame fr is not nil do not forget to ReleaseFrame(fr) This function responds automatically to PING and PONG messages.

func (*Conn) ReadFrame

func (conn *Conn) ReadFrame(fr *Frame) (nn int, err error)

ReadFrame fills fr with the next connection frame.

func (*Conn) ReadFull

func (conn *Conn) ReadFull(b []byte, fr *Frame) ([]byte, error)

ReadFull will read the parsed frame fully and writing the payload into b.

This function responds automatically to PING and PONG messages.

func (*Conn) ReadMessage

func (conn *Conn) ReadMessage(b []byte) (Mode, []byte, error)

ReadMessage reads next message from conn and returns the mode, b and/or error.

b is used to avoid extra allocations and can be nil.

This function responds automatically to PING and PONG messages.

func (*Conn) RemoteAddr

func (conn *Conn) RemoteAddr() net.Addr

RemoteAddr returns peer remote address.

func (*Conn) ReplyClose

func (conn *Conn) ReplyClose(fr *Frame) (err error)

ReplyClose is used to reply to CodeClose.

func (*Conn) Reset

func (conn *Conn) Reset(c net.Conn)

Reset resets conn values setting c as default connection endpoint.

func (*Conn) SendCode

func (conn *Conn) SendCode(code Code, status StatusCode, b []byte) error

SendCode writes code, status and message to conn.

status is used by CodeClose to report any close status (as HTTP responses). Can be 0. b can be nil.

func (*Conn) SendCodeString

func (conn *Conn) SendCodeString(code Code, status StatusCode, b string) error

SendCodeString writes code, status and message to conn as SendCode does.

func (*Conn) SetUserValue

func (conn *Conn) SetUserValue(key string, value interface{})

SetUserValue assigns a key to the given value

func (*Conn) UserValue

func (conn *Conn) UserValue(key string) interface{}

UserValue returns the key associated value.

func (*Conn) Write

func (conn *Conn) Write(b []byte) (int, error)

Write writes b using conn.Mode as default.

func (*Conn) WriteFrame

func (conn *Conn) WriteFrame(fr *Frame) (int, error)

WriteFrame writes fr to the connection endpoint.

func (*Conn) WriteMessage

func (conn *Conn) WriteMessage(mode Mode, b []byte) (int, error)

WriteMessage writes b to conn using mode.

func (*Conn) WriteString

func (conn *Conn) WriteString(b string) (int, error)

WriteString writes b to conn using conn.Mode as default.

type Frame

type Frame struct {
	// contains filtered or unexported fields
}

Frame is the unit used to transfer message between endpoints using the websocket protocol.

func AcquireFrame

func AcquireFrame() *Frame

AcquireFrame gets Frame from the global pool.

func (*Frame) Code

func (fr *Frame) Code() Code

Code returns the code set in fr.

func (*Frame) CopyTo

func (fr *Frame) CopyTo(fr2 *Frame)

CopyTo copies the frame `fr` to `fr2`

func (*Frame) HasRSV1

func (fr *Frame) HasRSV1() bool

HasRSV1 checks if RSV1 bit is set.

func (*Frame) HasRSV2

func (fr *Frame) HasRSV2() bool

HasRSV2 checks if RSV2 bit is set.

func (*Frame) HasRSV3

func (fr *Frame) HasRSV3() bool

HasRSV3 checks if RSV3 bit is set.

func (*Frame) IsClose

func (fr *Frame) IsClose() bool

IsClose returns true if Code is CodeClose.

func (*Frame) IsContinuation

func (fr *Frame) IsContinuation() bool

IsContinuation returns true if the Frame code is Continuation

func (*Frame) IsControl

func (fr *Frame) IsControl() bool

IsControl returns whether the Frame is a control frame or not. That means if it's a Close, Ping or Pong frame.

func (*Frame) IsFin

func (fr *Frame) IsFin() bool

IsFin checks if FIN bit is set.

func (*Frame) IsMasked

func (fr *Frame) IsMasked() bool

IsMasked checks if Mask bit is set.

func (*Frame) IsPing

func (fr *Frame) IsPing() bool

IsPing returns true if Code is CodePing.

func (*Frame) IsPong

func (fr *Frame) IsPong() bool

IsPong returns true if Code is CodePong.

func (*Frame) Len

func (fr *Frame) Len() (length uint64)

Len returns the length of the payload based on the header bits.

If you want to know the actual payload length use #PayloadLen

func (*Frame) Mask

func (fr *Frame) Mask()

Mask performs the masking of the current payload

func (*Frame) MaskKey

func (fr *Frame) MaskKey() []byte

MaskKey returns mask key.

Returns zero-padded if doesn't have a mask

func (*Frame) Mode

func (fr *Frame) Mode() (mode Mode)

Mode returns frame mode.

func (*Frame) Payload

func (fr *Frame) Payload() []byte

Payload returns the frame payload.

func (*Frame) PayloadLen

func (fr *Frame) PayloadLen() int

PayloadLen returns the actual payload length

func (*Frame) PayloadSize

func (fr *Frame) PayloadSize() uint64

PayloadSize returns the max payload size

func (*Frame) ReadFrom

func (fr *Frame) ReadFrom(rd io.Reader) (int64, error)

ReadFrom fills fr reading from rd.

func (*Frame) Reset

func (fr *Frame) Reset()

Reset resets all Frame values to the default.

func (*Frame) SetBinary

func (fr *Frame) SetBinary()

SetBinary sets CodeText in Code field.

func (*Frame) SetClose

func (fr *Frame) SetClose()

SetClose sets CodeClose in Code field.

func (*Frame) SetCode

func (fr *Frame) SetCode(code Code)

SetCode sets code bits.

func (*Frame) SetContinuation

func (fr *Frame) SetContinuation()

SetContinuation sets CodeContinuation in Code field.

func (*Frame) SetFin

func (fr *Frame) SetFin()

SetFin sets FIN bit.

func (*Frame) SetMask

func (fr *Frame) SetMask(b []byte)

SetMask sets the first 4 parsed bytes as mask key and enables the mask bit

func (*Frame) SetPayload

func (fr *Frame) SetPayload(b []byte)

SetPayload sets the parsed bytes as frame's payload

func (*Frame) SetPayloadSize

func (fr *Frame) SetPayloadSize(size uint64)

SetPayloadSize sets max payload size

func (*Frame) SetPing

func (fr *Frame) SetPing()

SetPing sets CodePing in Code field.

func (*Frame) SetPong

func (fr *Frame) SetPong()

SetPong sets CodePong in Code field.

func (*Frame) SetRSV1

func (fr *Frame) SetRSV1()

SetRSV1 sets RSV1 bit.

func (*Frame) SetRSV2

func (fr *Frame) SetRSV2()

SetRSV2 sets RSV2 bit.

func (*Frame) SetRSV3

func (fr *Frame) SetRSV3()

SetRSV3 sets RSV3 bit.

func (*Frame) SetStatus

func (fr *Frame) SetStatus(status StatusCode)

SetStatus sets status code.

Status code is usually used in Close request.

func (*Frame) SetText

func (fr *Frame) SetText()

SetText sets CodeText in Code field.

func (*Frame) Status

func (fr *Frame) Status() (status StatusCode)

Status returns StatusCode.

func (*Frame) String

func (fr *Frame) String() string

String returns a representation of Frame in a human-readable string format.

func (*Frame) Unmask

func (fr *Frame) Unmask()

Unmask performs the unmasking of the current payload

func (*Frame) UnsetMask

func (fr *Frame) UnsetMask()

UnsetMask only drops the mask bit.

func (*Frame) Write

func (fr *Frame) Write(b []byte) (int, error)

Write appends the parsed bytes to the frame's payload

func (*Frame) WriteTo

func (fr *Frame) WriteTo(wr io.Writer) (n int64, err error)

WriteTo writes the frame into wr.

type Mode

type Mode uint8

Mode is the mode in which the bytes are sended.

https://tools.ietf.org/html/rfc6455#section-5.6

const (
	// ModeText defines to use a text mode
	ModeText Mode = iota
	// ModeBinary defines to use a binary mode
	ModeBinary
)

type NetUpgradeHandler

type NetUpgradeHandler func(resp http.ResponseWriter, req *http.Request) bool

NetUpgradeHandler is the upgrading handler for net/http.

type NetUpgrader

type NetUpgrader struct {
	// UpgradeHandler allows user to handle RequestCtx when upgrading.
	//
	// If UpgradeHandler returns false the connection won't be upgraded and
	// the parsed ctx will be used as a response.
	UpgradeHandler NetUpgradeHandler

	// Handler is the request handler for ws connections.
	Handler RequestHandler

	// Protocols are the supported protocols.
	Protocols []string

	// Origin is used to limit the clients coming from the defined origin
	Origin string

	// Compress defines whether using compression or not.
	// TODO
	Compress bool
}

NetUpgrader upgrades HTTP connection to a websocket connection if it's possible.

NetUpgrader executes NetUpgrader.Handler after successful websocket upgrading.

func (*NetUpgrader) Upgrade

func (upgr *NetUpgrader) Upgrade(resp http.ResponseWriter, req *http.Request)

Upgrade upgrades HTTP to websocket connection if possible.

If client does not request any websocket connection this function will execute ctx.NotFound()

When connection is successfully stablished the function calls s.Handler.

type RequestHandler

type RequestHandler func(conn *Conn)

RequestHandler is the websocket connection handler.

type StatusCode

type StatusCode uint16

StatusCode is sent when closing a connection.

The following constants have been defined by the RFC.

func (StatusCode) String

func (status StatusCode) String() string

type UpgradeHandler

type UpgradeHandler func(*fasthttp.RequestCtx) bool

UpgradeHandler is the upgrading handler.

type Upgrader

type Upgrader struct {
	// UpgradeHandler allows user to handle RequestCtx when upgrading.
	//
	// If UpgradeHandler returns false the connection won't be upgraded and
	// the parsed ctx will be used as a response.
	UpgradeHandler UpgradeHandler

	// Handler is the request handler for ws connections.
	Handler RequestHandler

	// Protocols are the supported protocols.
	Protocols []string

	// Origin is used to limit the clients coming from the defined origin
	Origin string

	// Compress defines whether using compression or not.
	// TODO
	Compress bool
}

Upgrader upgrades HTTP connection to a websocket connection if it's possible.

Upgrader executes Upgrader.Handler after successful websocket upgrading.

func (*Upgrader) Upgrade

func (upgr *Upgrader) Upgrade(ctx *fasthttp.RequestCtx)

Upgrade upgrades HTTP to websocket connection if possible.

If client does not request any websocket connection this function will execute ctx.NotFound()

When connection is successfully stablished the function calls s.Handler.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL