websocket

package module
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: May 10, 2025 License: MIT Imports: 12 Imported by: 0

README

websocket

Documentation Build status Code coverage Go report card

A zero-dependency Golang implementation of the websocket protocol (RFC 6455), originally extracted from mccutchen/go-httpbin.

[!WARNING] Not production ready!

Do not use this library in a production application. It is not particularly optimized and many breaking API changes are likely for the forseeable future.

Consider one of these libraries instead:

Usage

For now, see

More useful usage and example docs TK.

Testing

Unit tests

This project's unit tests are relatively quick to run and provide decent coverage of the implementation:

make test

Or generate code coverage:

make testcover
Autobahn integration tests

The crossbario/autobahn-testsuite project's "fuzzing client" is also used for integration/conformance/fuzz testing.

[!NOTE] The most recent Autobahn test reports may be viewed at https://mccutchen.github.io/websocket/

Because these tests a) require docker and b) take 40-60s to run, they are disabled by default.

To run the autobahn fuzzing client in its default configuration, use:

make testautobahn

There are a variety of options that can be enabled individually or together, which are useful for viewing the generated HTML test report, narrowing the set of test cases to debug a particular issue, or to enable more detailed debug logging.

  • AUTOBAHN=1 is required to enable the Autobahn fuzzing client test suite, set automatically by the make testautobahn target.

  • CASES narrows the set of test cases to execute (note the wildcards):

    make testautobahn CASES='5.7,6.12.*,9.*'
    
  • REPORT_DIR={path} specifies the output dir for autobahn test results (defaults to .ingegrationtests/autobahn-test-${timestamp})

  • REPORT=1 automatically opens the resulting HTML test resport:

    make testautobahn REPORT=1
    
  • TARGET={url} runs autobanh against an external server instead of an ephemeral httptest server, which can be useful for, e.g., capturing pprof info or running tcpdump:

    make testautobahn TARGET=http://localhost:8080/
    
  • DEBUG=1 enables fairly detailed debug logging via the server's built-in websocket lifecycle hooks:

    make testautobahn DEBUG=1
    

Putting it all together, a command like this might be used to debug a a particular failing test case:

make testautobahn DEBUG=1 CASES=9.1.6 REPORT=1

Benchmarks

🚧 Accurate, useful benchmarks are very much a work in progress. 🚧

Go benchmarks

Standard Go benchmarks may be run like so:

make bench
nstd/webocket-benchmarks

Basic, manual support for running the ntsd/websocket-benchmarks suite of benchmarks is documented in the examples/benchserver dir.

Documentation

Overview

Package websocket implements a basic websocket server.

Index

Constants

View Source
const (
	DefaultMaxFrameSize   int = 16 << 10  // 16KiB
	DefaultMaxMessageSize int = 256 << 10 // 256KiB
)

Default options.

Variables

View Source
var (
	ErrClientFrameUnmasked    = newError(StatusProtocolError, "client frame must be masked")
	ErrClosePayloadInvalid    = newError(StatusProtocolError, "close frame payload must be at least 2 bytes")
	ErrCloseStatusInvalid     = newError(StatusProtocolError, "close status code out of range")
	ErrCloseStatusReserved    = newError(StatusProtocolError, "close status code is reserved")
	ErrContinuationExpected   = newError(StatusProtocolError, "continuation frame expected")
	ErrContinuationUnexpected = newError(StatusProtocolError, "continuation frame unexpected")
	ErrControlFrameFragmented = newError(StatusProtocolError, "control frame must not be fragmented")
	ErrControlFrameTooLarge   = newError(StatusProtocolError, "control frame payload exceeds 125 bytes")
	ErrInvalidFramePayload    = newError(StatusInvalidFramePayload, "payload must be valid UTF-8")
	ErrFrameTooLarge          = newError(StatusTooLarge, "frame payload too large")
	ErrMessageTooLarge        = newError(StatusTooLarge, "message paylaod too large")
	ErrOpcodeUnknown          = newError(StatusProtocolError, "opcode unknown")
	ErrRSVBitsUnsupported     = newError(StatusProtocolError, "RSV bits not supported")
)

Protocol-level errors.

View Source
var Unmasked = MaskingKey([4]byte{})

Unmasked is the zero MaskingKey, indicating that a marshaled frame should not be masked.

Functions

func MarshalFrame added in v0.0.2

func MarshalFrame(frame *Frame, mask MaskingKey) []byte

MarshalFrame marshals a Frame into bytes for transmission.

func WriteFrame

func WriteFrame(dst io.Writer, mask MaskingKey, frame *Frame) error

WriteFrame writes a Frame to dst with the given masking key. To write an unmasked frame, use the special Unmasked key.

Types

type ClientKey

type ClientKey string

ClientKey identifies the client in the websocket handshake.

func Handshake

func Handshake(w http.ResponseWriter, r *http.Request) (ClientKey, error)

Handshake is a low-level helper that validates the request and performs the WebSocket Handshake, after which only websocket frames should be written to the underlying connection.

Prefer the higher-level Accept API when possible.

func NewClientKey

func NewClientKey() ClientKey

NewClientKey returns a randomly-generated ClientKey for client handshake. Panics on insufficient randomness.

type Error added in v0.0.2

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

Error is a websocket error with an associated StatusCode.

func (*Error) Error added in v0.0.2

func (e *Error) Error() string

func (*Error) Unwrap added in v0.0.2

func (e *Error) Unwrap() error

type Frame

type Frame struct {
	Payload []byte
	// contains filtered or unexported fields
}

Frame is a websocket frame.

func FrameMessage added in v0.0.2

func FrameMessage(msg *Message, frameSize int) []*Frame

FrameMessage splits a message into N frames with payloads of at most frameSize bytes.

func NewCloseFrame added in v0.0.2

func NewCloseFrame(code StatusCode, reason string) *Frame

NewCloseFrame creates a close Frame. If the given StatusCode is 0, the close frame will not have a payload.

func NewFrame added in v0.0.2

func NewFrame(opcode Opcode, fin bool, payload []byte, rsv ...RSVBit) *Frame

NewFrame creates a new Frame with the given Opcode, fin bit, and payload.

func ReadFrame

func ReadFrame(src io.Reader, mode Mode, maxPayloadLen int) (*Frame, error)

ReadFrame reads a Frame.

func (*Frame) Fin

func (f *Frame) Fin() bool

Fin returns a bool indicating whether the frame's FIN bit is set.

func (*Frame) Opcode

func (f *Frame) Opcode() Opcode

Opcode returns the the frame's Opcode.

func (*Frame) RSV1

func (f *Frame) RSV1() bool

RSV1 returns true if the frame's RSV1 bit is set

func (*Frame) RSV2

func (f *Frame) RSV2() bool

RSV2 returns true if the frame's RSV2 bit is set

func (*Frame) RSV3

func (f *Frame) RSV3() bool

RSV3 returns true if the frame's RSV3 bit is set

func (Frame) String

func (f Frame) String() string

type Handler

type Handler func(ctx context.Context, msg *Message) (*Message, error)

Handler handles a single websocket Message as part of the high level [Serve] request-response API.

If the returned message is non-nil, it will be sent to the client. If an error is returned, the connection will be closed.

var EchoHandler Handler = func(_ context.Context, msg *Message) (*Message, error) {
	return msg, nil
}

EchoHandler is a Handler that echoes each incoming Message back to the client.

type Hooks

type Hooks struct {
	// OnClose is called when the connection is closed.
	OnClose func(ClientKey, StatusCode, error)
	// OnReadError is called when a read error occurs.
	OnReadError func(ClientKey, error)
	// OnReadFrame is called when a frame is read.
	OnReadFrame func(ClientKey, *Frame)
	// OnReadMessage is called when a complete message is read.
	OnReadMessage func(ClientKey, *Message)
	// OnWriteError is called when a write error occurs.
	OnWriteError func(ClientKey, error)
	// OnWriteFrame is called when a frame is written.
	OnWriteFrame func(ClientKey, *Frame)
	// OnWriteMessage is called when a complete message is written.
	OnWriteMessage func(ClientKey, *Message)
}

Hooks define the callbacks that are called during the lifecycle of a websocket connection.

type MaskingKey

type MaskingKey [4]byte

MaskingKey masks a client Frame.

func NewMaskingKey

func NewMaskingKey() MaskingKey

NewMaskingKey returns a randomly generated MaskingKey for client frames. Panics on insufficient randomness.

type Message

type Message struct {
	Binary  bool
	Payload []byte
}

Message is an application-level message from the client, which may be constructed from one or more individual frames.

func (Message) String

func (m Message) String() string

type Mode

type Mode bool

Mode enalbes server or client behavior

const (
	ServerMode Mode = false
	ClientMode      = true
)

Valid modes

type Opcode

type Opcode uint8

Opcode is a websocket OPCODE.

const (
	OpcodeContinuation Opcode = 0b0000_0000 // 0x0
	OpcodeText         Opcode = 0b0000_0001 // 0x1
	OpcodeBinary       Opcode = 0b0000_0010 // 0x2
	OpcodeClose        Opcode = 0b0000_1000 // 0x8
	OpcodePing         Opcode = 0b0000_1001 // 0x9
	OpcodePong         Opcode = 0b0000_1010 // 0xA
)

See the RFC for the set of defined opcodes: https://datatracker.ietf.org/doc/html/rfc6455#section-5.2

func (Opcode) String

func (c Opcode) String() string

type Options

type Options struct {
	Hooks          Hooks
	ReadTimeout    time.Duration
	WriteTimeout   time.Duration
	MaxFrameSize   int
	MaxMessageSize int
}

Options define the limits imposed on a websocket connection.

type RSVBit added in v0.0.2

type RSVBit byte

RSVBit is a bit mask for RSV bits 1-3

const (
	RSV1 RSVBit = 0b0100_0000
	RSV2 RSVBit = 0b0010_0000
	RSV3 RSVBit = 0b0001_0000
)

RSV bits 1-3

type StatusCode

type StatusCode uint16

StatusCode is a websocket close status code.

const (
	StatusNormalClosure       StatusCode = 1000
	StatusGoingAway           StatusCode = 1001
	StatusProtocolError       StatusCode = 1002
	StatusUnsupportedData     StatusCode = 1003
	StatusNoStatusRcvd        StatusCode = 1005
	StatusAbnormalClose       StatusCode = 1006
	StatusInvalidFramePayload StatusCode = 1007
	StatusPolicyViolation     StatusCode = 1008
	StatusTooLarge            StatusCode = 1009
	StatusMandatoryExtension  StatusCode = 1010
	StatusInternalError       StatusCode = 1011
	StatusServiceRestart      StatusCode = 1012
	StatusTryAgainLater       StatusCode = 1013
	StatusGatewayError        StatusCode = 1014
	StatusTlSHandshake        StatusCode = 1015
)

Close status codes, as defined in RFC 6455 and the IANA registry.

https://datatracker.ietf.org/doc/html/rfc6455#section-7.4.1 https://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number

type Websocket

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

Websocket is a websocket connection.

func Accept

func Accept(w http.ResponseWriter, r *http.Request, opts Options) (*Websocket, error)

Accept handles the initial HTTP-based handshake and upgrades the TCP connection to a websocket connection.

func New

func New(src io.ReadWriteCloser, clientKey ClientKey, mode Mode, opts Options) *Websocket

New is a low-level API that manually creates a new websocket connection. Caller is responsible for completing initial handshake before creating a websocket connection.

Prefer the higher-level Accept API when possible. See also Handshake if using New directly.

func (*Websocket) ClientKey

func (ws *Websocket) ClientKey() ClientKey

ClientKey returns the ClientKey for a connection.

func (*Websocket) Close

func (ws *Websocket) Close() error

Close closes a websocket connection.

func (*Websocket) ReadMessage

func (ws *Websocket) ReadMessage(ctx context.Context) (*Message, error)

ReadMessage reads a single Message from the connection, handling fragments and control frames automatically. The connection will be closed on any error.

func (*Websocket) Serve

func (ws *Websocket) Serve(ctx context.Context, handler Handler)

Serve is a high-level convienience method for request-response style websocket connections, where the given Handler is called for each incoming message and its return value is sent back to the client.

See also EchoHandler.

func (*Websocket) WriteMessage

func (ws *Websocket) WriteMessage(ctx context.Context, msg *Message) error

WriteMessage writes a single Message to the connection, after splitting it into fragments (if necessary). The connection will be closed on any error.

Directories

Path Synopsis
internal
testing/assert
Package assert implements common assertions used in go-httbin's unit tests.
Package assert implements common assertions used in go-httbin's unit tests.
testing/must
Package must implements helper functions for testing to eliminate some error checking boilerplate.
Package must implements helper functions for testing to eliminate some error checking boilerplate.

Jump to

Keyboard shortcuts

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