Skip to content

net.websocket #

Websocket callbacks

Websocket event callbacks have result signatures, such as fn (mut client websocket.Client, message &websocket.Message) !. Callbacks that return void can also be registered: returning normally is treated as success. The C backend adapts their return value so the listener receives a valid successful result.

For callbacks registered with on_message_ref, the last voidptr argument is the original reference passed at registration. Cast it back to its concrete reference type in an unsafe block, and keep the referenced object alive while the callback can run. Such a reference can be forwarded as mut unsafe { &Context(ctx) } to a function taking mut context &Context.

A server's on_connect callback runs before its handshake response is written. Use on_attached or on_attached_ref to push frames after the handshake response has been written and the client's callbacks have been registered.

Explicit write batching

Opening handshakes reject empty input and malformed header lines with an error. Each nonempty header line needs a name followed by a colon; values may contain further colons.

client.write_messages([]websocket.Message) sends the supplied complete frames in order without waiting to collect more traffic. It uses one socket write and returns the wire byte count, including headers. Empty batches are a no-op. Control frames must be at most 125 bytes; continuation frames are not accepted. The same control payload limit applies to write, write_ptr, and close. For close, the two-byte status code leaves at most 123 bytes for the reason. An oversized reason sends no frame bytes and returns an error; the usual socket shutdown, state reset, and close event still run. Close frames are not accepted; call client.close(code, reason) for the closing handshake. The entire batch is validated before any frames are sent. Socket writes, including pongs from the reader, are serialized per connection. On a write error, close the connection: a prefix may already have been sent.

Incremental server frame decoding

ServerFrameDecoder.decode(mut input) processes masked client frames from a caller-owned buffer without performing socket I/O. It supports incomplete input, fragmented messages, interleaved controls, message limits, and protocol validation. See the decoder documentation for buffer ownership and usage.

Optional Linux reactor

new_reactor() serves already upgraded plaintext TCP sockets on an explicit worker thread using epoll and eventfd. It provides bounded input/output work, cross-thread send handles, backpressure, deadlines, and graceful shutdown. The existing Client and Server APIs keep their execution model. See the reactor documentation for ownership, callbacks, and limits. See text validation for the ASCII fast path and Unicode fallback.

Incoming 64-bit frame lengths must have the most significant bit clear and fit in V's int range. Oversized lengths are rejected before allocating the payload buffer.

Owning a client connection

Plain ws:// clients and accepted server connections do not create TLS contexts. Only wss:// clients allocate them, including the fresh context retained for reconnection after close.

client.connect_with_dialer(dialer) accepts a callback with the signature fn (address string) !&net.TcpConn. The callback returns a connected TCP socket and releases any failed candidates itself. After it returns successfully, the WebSocket client owns the socket, applies the configured timeouts and TLS, and performs the opening handshake. A custom dialer must provide any proxy tunnel itself; proxy_url is rejected on this path.

After joining all readers and writers, call client.dispose() to release the connection and TLS resources. It is safe before connecting, after a failed connection, or more than once. Disposal is terminal: create a new client for a later connection.

fn new_client #

fn new_client(address string, opt ClientOpt) !&Client

new_client instance a new websocket client

fn new_reactor #

Available on Linux only.

fn new_reactor(options ReactorOptions) !&Reactor

new_reactor allocates the poller. Call run exactly once, even if stop was requested before startup, then join the worker before discarding the reactor.

fn new_server #

fn new_server(family net.AddrFamily, port int, route string, opt ServerOpt) &Server

new_server instance a new websocket server on provided port and route

fn FrameDecodeKind.from #

fn FrameDecodeKind.from[W](input W) !FrameDecodeKind

from returns the FrameDecodeKind value of an integer or of a field name, or an error if there is none.

fn OPCode.from #

fn OPCode.from[W](input W) !OPCode

from returns the OPCode value of an integer or of a field name, or an error if there is none.

fn State.from #

fn State.from[W](input W) !State

from returns the State value of an integer or of a field name, or an error if there is none.

type AcceptClientFn #

type AcceptClientFn = fn (mut c ServerClient) !bool

type ServerAttachedFn #

type ServerAttachedFn = fn (mut c ServerClient) !

type ServerAttachedFn2 #

type ServerAttachedFn2 = fn (mut c ServerClient, v voidptr) !

type SocketCloseFn #

type SocketCloseFn = fn (mut c Client, code int, reason string) !

type SocketCloseFn2 #

type SocketCloseFn2 = fn (mut c Client, code int, reason string, v voidptr) !

type SocketErrorFn #

type SocketErrorFn = fn (mut c Client, err string) !

type SocketErrorFn2 #

type SocketErrorFn2 = fn (mut c Client, err string, v voidptr) !

type SocketMessageFn #

type SocketMessageFn = fn (mut c Client, msg &Message) !

type SocketMessageFn2 #

type SocketMessageFn2 = fn (mut c Client, msg &Message, v voidptr) !

type SocketOpenFn #

type SocketOpenFn = fn (mut c Client) !

type SocketOpenFn2 #

type SocketOpenFn2 = fn (mut c Client, v voidptr) !

fn (Uri) str #

fn (u Uri) str() string

str returns the string representation of the Uri

enum FrameDecodeKind #

enum FrameDecodeKind {
	need_more
	fragment
	message
	control
	failure
}

FrameDecodeKind distinguishes an incomplete frame, an intermediate fragment, a complete message, a control frame, and a terminal protocol error.

enum OPCode #

enum OPCode {
	continuation = 0x00
	text_frame   = 0x01
	binary_frame = 0x02
	close        = 0x08
	ping         = 0x09
	pong         = 0x0A
}

OPCode represents the supported websocket frame types

enum State #

enum State {
	connecting = 0
	open
	closing
	closed
}

State represents the state of the websocket connection.

struct Client #

struct Client {
	is_server bool
mut:
	read_buffer       [8192]u8
	read_start        int
	read_end          int
	ssl_conn          &ssl.SSLConn = unsafe { nil } // secure connection used when wss is used
	proxy_url         string
	flags             []Flag                // flags used in handshake
	fragments         []Fragment            // current fragments
	message_callbacks []MessageEventHandler // all callbacks on_message
	error_callbacks   []ErrorEventHandler   // all callbacks on_error
	open_callbacks    []OpenEventHandler    // all callbacks on_open
	close_callbacks   []CloseEventHandler   // all callbacks on_close
	write_lock        shared WriteLock
pub:
	is_ssl        bool   // true if secure socket is used
	uri           Uri    // uri of current connection
	id            string // unique id of client
	read_timeout  i64
	write_timeout i64
pub mut:
	header            http.Header // headers that will be passed when connecting
	conn              &net.TcpConn = unsafe { nil } // underlying TCP socket connection
	nonce_size        int          = 16           // size of nounce used for masking
	panic_on_callback bool               // set to true of callbacks can panic
	client_state      shared ClientState // current state of connection
	// logger used to log messages
	logger        &log.Logger = default_logger
	resource_name string // name of current resource
	last_pong_ut  i64    // last time in unix time we got a pong message
}

Client represents websocket client

fn (Client) on_message #

fn (mut ws Client) on_message(fun SocketMessageFn)

on_message registers a callback on new messages

fn (Client) on_message_ref #

fn (mut ws Client) on_message_ref(fun SocketMessageFn2, ref voidptr)

on_message_ref registers a callback on new messages and provides a reference object

fn (Client) on_error #

fn (mut ws Client) on_error(fun SocketErrorFn)

on_error registers a callback on errors

fn (Client) on_error_ref #

fn (mut ws Client) on_error_ref(fun SocketErrorFn2, ref voidptr)

on_error_ref registers a callback on errors and provides a reference object

fn (Client) on_open #

fn (mut ws Client) on_open(fun SocketOpenFn)

on_open registers a callback on successful opening the websocket

fn (Client) on_open_ref #

fn (mut ws Client) on_open_ref(fun SocketOpenFn2, ref voidptr)

on_open_ref registers a callback on successful opening the websocket and provides a reference object

fn (Client) on_close #

fn (mut ws Client) on_close(fun SocketCloseFn)

on_close registers a callback on closed socket

fn (Client) on_close_ref #

fn (mut ws Client) on_close_ref(fun SocketCloseFn2, ref voidptr)

on_close_ref registers a callback on closed socket and provides a reference object

fn (Client) validate_frame #

fn (mut ws Client) validate_frame(frame &Frame) !

validate_client validates client frame rules from RFC6455

fn (Client) read_next_message #

fn (mut ws Client) read_next_message() !Message

read_next_message reads 1 to n frames to compose a message

fn (Client) parse_frame_header #

fn (mut ws Client) parse_frame_header() !Frame

parse_frame_header parses next message by decoding the incoming frames

fn (Client) connect #

fn (mut ws Client) connect() !

connect connects to remote websocket server

fn (Client) connect_with_dialer #

fn (mut ws Client) connect_with_dialer(dialer fn (string) !&net.TcpConn) !

connect_with_dialer uses an owned TCP connection supplied by the caller. The dialer must return a connected socket and owns failed candidates. Once returned, this client owns cleanup. TLS and HTTP upgrade use the usual path. Custom dialers provide their own proxy tunneling; ClientOpt.proxy_url is not accepted here to avoid silently bypassing configured proxy behavior.

fn (Client) dispose #

fn (mut ws Client) dispose()

dispose ends a client generation after its reader and writers have joined. Unlike close (which supports reconnect), it also releases the freshly reset TLS context, or the initial context when dialing never produced Client.conn. It is idempotent; this client must not be reconnected after disposal.

fn (Client) listen #

fn (mut ws Client) listen() !

listen listens and processes incoming messages

fn (Client) ping #

fn (mut ws Client) ping() !

ping sends ping message to server

fn (Client) pong #

fn (mut ws Client) pong() !

pong sends pong message to server,

fn (Client) write_ptr #

fn (mut ws Client) write_ptr(bytes &u8, payload_len int, code OPCode) !int

write_ptr writes payload_len bytes from a pointer with a websocket message type. Control frame payloads larger than 125 bytes are rejected before socket access.

fn (Client) write #

fn (mut ws Client) write(bytes []u8, code OPCode) !int

write writes a byte array with a websocket message type to the socket. Control frame payloads may contain at most 125 bytes.

fn (Client) write_string #

fn (mut ws Client) write_string(str string) !int

write_str, writes a string with a websocket texttype to socket

fn (Client) close #

fn (mut ws Client) close(code int, message string) !

close closes the websocket connection. A positive code leaves at most 123 bytes for the reason. An oversized reason returns an error and still shuts down, resets, and emits the close event.

fn (Client) set_state #

fn (mut ws Client) set_state(state State)

set_state sets current state of the websocket connection

fn (Client) get_state #

fn (ws &Client) get_state() State

get_state return the current state of the websocket connection

fn (Client) reset_state #

fn (mut ws Client) reset_state() !

reset_state resets the websocket and initialize default settings

fn (Client) free #

fn (c &Client) free()

free handles manual free memory of Client struct

fn (Client) write_messages #

fn (mut ws Client) write_messages(messages []Message) !int

write_messages sends complete messages in order with one socket write. It does not wait for more messages or retain caller buffers after returning. The return value includes frame headers, as with write(). A failed write may have sent a prefix; close the connection instead of replaying the batch. Close frames are not accepted; use close() to perform the closing handshake.

struct ClientOpt #

@[params]
struct ClientOpt {
pub:
	read_timeout  i64         = net.infinite_timeout
	write_timeout i64         = 30 * time.second
	logger        &log.Logger = default_logger
	proxy_url     string // optional proxy URL used to open the websocket TCP tunnel
}

struct ClientState #

struct ClientState {
pub mut:
	state State = .closed // current state of connection
}

struct DecodedFrame #

struct DecodedFrame {
pub:
	kind       FrameDecodeKind
	consumed   int
	opcode     OPCode = .continuation
	payload    []u8
	close_code int
	reason     string
}

DecodedFrame borrows payload from the input or decoder until the next decode call. consumed bytes may be discarded only after the payload has been used. A failure carries a WebSocket close code and reason, not a consumed payload.

struct Message #

struct Message {
pub:
	opcode  OPCode // websocket frame type of this message
	payload []u8   // payload of the message
}

Message represents a whole message combined from 1 to n frames

fn (Message) free #

fn (m &Message) free()

free handles manual free memory of Message struct

struct Reactor #

@[heap]

Available on Linux only.

struct Reactor {
	options  ReactorOptions
	epoll_fd int
	wake_fd  int
mut:
	owner_thread   u64
	serial         u64
	started        u64
	stop_requested u64
	inbox          shared ReactorInbox
	payload_spare  []u8
	sockets        map[u64]&ReactorSocket
	dirty          []u64
	readable       []u64
	closed         []ReactorClosed
	stopping       bool
}

Reactor owns all socket I/O on its run() thread. HTTP validates the upgrade and transfers TCP ownership with attach. Create several reactors for multiple workers. Existing Client and Server APIs keep their execution model.

fn (Reactor) attach #

Available on Linux only.

fn (mut r Reactor) attach(mut conn net.TcpConn, response string) !&ReactorClient

attach reserves capacity synchronously and transfers conn only on success. response is the HTTP 101 response, queued before any WebSocket output. Stop accessing conn after success. A later setup failure invokes on_close.

fn (Reactor) stop #

Available on Linux only.

fn (mut r Reactor) stop()

stop rejects new work immediately and requests a bounded graceful shutdown. It is idempotent and safe from a callback or another thread. Join run after it.

fn (Reactor) run #

Available on Linux only.

fn (mut r Reactor) run() !

run executes exactly once and releases all descriptors before returning. Monotonic IDs protect descriptor reuse. Reads, frames, and writes have per-turn budgets; partial sends resume at their byte offset. Deadline sweeps are 50 ms.

struct ReactorClient #

@[heap]

Available on Linux only.

struct ReactorClient {
pub:
	id string
mut:
	owner      &Reactor = unsafe { nil }
	key        u64
	state      u64 // 0 pending, 1 open, 2 closing, 3 closed
	overloaded u64
}

ReactorClient is a thread-safe handle. Successful methods accept commands, not delivery receipts. Every successful attach has exactly one on_close, including an asynchronous failure before on_open. Failed attach retains the caller's socket ownership and produces no callbacks.

fn (ReactorClient) is_open #

Available on Linux only.

fn (c &ReactorClient) is_open() bool

is_open reports whether the worker has attached the connection and no close has been requested. It is a snapshot, not a delivery guarantee.

fn (ReactorClient) write #

Available on Linux only.

fn (mut c ReactorClient) write(payload []u8, opcode OPCode) !int

write accepts a final text, binary, ping, or pong frame. Control payloads are limited to 125 bytes. Cross-thread payloads are copied before returning.

fn (ReactorClient) write_string #

Available on Linux only.

fn (mut c ReactorClient) write_string(text string) !int

write_string accepts a final text frame. Success means accepted, not delivered.

fn (ReactorClient) close #

Available on Linux only.

fn (mut c ReactorClient) close(code int, reason string) !

close starts a bounded closing handshake after accepted output. New sends fail once close is accepted. TCP closes after the peer reply or close_timeout.

fn (ReactorClient) set_read_timeout #

Available on Linux only.

fn (mut c ReactorClient) set_read_timeout(timeout time.Duration) !

set_read_timeout changes the connection's read inactivity deadline.

struct ReactorOptions #

@[params]

Available on Linux only.

struct ReactorOptions {
pub:
	max_message_bytes    int                                          = 16384
	max_pending_messages int                                          = 64
	max_pending_bytes    int                                          = 2 * 1024 * 1024
	max_connections      int                                          = 10000
	max_commands         int                                          = 8192
	max_command_bytes    int                                          = 16 * 1024 * 1024
	frames_per_turn      int                                          = 256
	read_timeout         time.Duration                                = 5 * time.second
	write_timeout        time.Duration                                = 5 * time.second
	close_timeout        time.Duration                                = time.second
	on_open              fn (mut ReactorClient, voidptr)              = reactor_no_open
	on_message           fn (mut ReactorClient, &Message, voidptr)    = reactor_no_message
	on_close             fn (mut ReactorClient, int, string, voidptr) = reactor_no_close
	user                 voidptr
}

ReactorOptions configures an optional Linux plaintext server transport. Callbacks run serially on the owning worker, must not block, and borrow each message payload only until on_message returns. Clone retained payloads.

struct Server #

struct Server {
mut:
	logger                  &log.Logger      = default_logger
	ls                      &net.TcpListener = unsafe { nil } // listener used to get incoming connection to socket
	accept_client_callbacks []AcceptClientFn // accept client callback functions
	attached_callbacks      []AttachedEventHandler
	message_callbacks       []MessageEventHandler // new message callback functions
	close_callbacks         []CloseEventHandler   // close message callback functions
pub:
	family net.AddrFamily = .ip
	port   int  // port used as listen to incoming connections
	is_ssl bool // true if secure connection (not supported yet on server)
pub mut:
	server_state shared ServerState
}

Server represents a websocket server connection

fn (Server) on_connect #

fn (mut s Server) on_connect(fun AcceptClientFn) !

on_connect registers a callback when client connects to the server

fn (Server) on_message #

fn (mut s Server) on_message(fun SocketMessageFn)

on_message registers a callback on new messages

fn (Server) on_message_ref #

fn (mut s Server) on_message_ref(fun SocketMessageFn2, ref voidptr)

on_message_ref registers a callback on new messages and provides a reference object

fn (Server) on_close #

fn (mut s Server) on_close(fun SocketCloseFn)

on_close registers a callback on closed socket

fn (Server) on_close_ref #

fn (mut s Server) on_close_ref(fun SocketCloseFn2, ref voidptr)

on_close_ref registers a callback on closed socket and provides a reference object

fn (Server) on_attached #

fn (mut s Server) on_attached(fun ServerAttachedFn)

on_attached registers a callback after a client has been attached to the server, the handshake response has been written, and callbacks are set up, but before the blocking listen loop begins.

fn (Server) on_attached_ref #

fn (mut s Server) on_attached_ref(fun ServerAttachedFn2, ref voidptr)

on_attached_ref registers a callback after a client has been attached to the server and provides a reference object.

fn (Server) set_ping_interval #

fn (mut s Server) set_ping_interval(seconds int)

set_ping_interval sets the interval that the server will send ping messages to clients

fn (Server) get_ping_interval #

fn (mut s Server) get_ping_interval() int

get_ping_interval return the interval that the server will send ping messages to clients

fn (Server) listen #

fn (mut s Server) listen() !

listen start listen and process to incoming connections from websocket clients

fn (Server) handle_handshake #

fn (mut s Server) handle_handshake(mut conn net.TcpConn, key string) !&ServerClient

handle_handshake use an existing connection to respond to the handshake for a given key

fn (Server) set_state #

fn (mut s Server) set_state(state State)

set_state sets current state in a thread safe way

fn (Server) get_state #

fn (s &Server) get_state() State

get_state return current state in a thread safe way

fn (Server) free #

fn (mut s Server) free()

free manages manual free of memory for Server instance

struct ServerClient #

struct ServerClient {
pub:
	resource_name string // resource that the client access
	client_key    string // unique key of client
pub mut:
	server &Server = unsafe { nil }
	client &Client = unsafe { nil }
}

ServerClient represents a connected client

struct ServerFrameDecoder #

struct ServerFrameDecoder {
pub:
	max_message_bytes int = 16384
mut:
	fragment        []u8
	fragment_opcode u8
	fragment_done   bool
	text_state      FrameUtf8
	failure_code    int
	failure_reason  string
}

ServerFrameDecoder incrementally decodes masked client frames without socket I/O. Keep incomplete input and append more bytes before retrying decode. One instance belongs to one connection and must not be shared concurrently.

fn (ServerFrameDecoder) decode #

fn (mut decoder ServerFrameDecoder) decode(mut input []u8) DecodedFrame

decode consumes at most one complete frame. It checks declared lengths before allocation, unmasks complete payloads in place, combines fragments, and checks UTF-8, close payloads, reserved bits/opcodes, and canonical lengths. Incomplete input is not modified. An error is terminal: create a new decoder to reset it.

struct ServerOpt #

@[params]
struct ServerOpt {
pub:
	logger &log.Logger = default_logger
}

struct ServerState #

struct ServerState {
mut:
	ping_interval int   = 30      // interval for sending ping to clients (seconds)
	state         State = .closed // current state of connection
pub mut:
	clients map[string]&ServerClient // clients connected to this server
}