# WebSockets
`http/ws-*` provides RFC 6455 framed bidirectional streams — both
client and server — over plain `ws://` (and `wss://` from the client
side via mbedTLS). The API mirrors `net/*` and uses the same per-fd
epoll callbacks.
## `http/ws-*` Stdlib functions
| Function | Returns | Purpose |
|---|---|---|
| `[http/ws-server $port %{ :on-connect $fn }]` | ws_server resource | Start a WebSocket server |
| `[http/ws-connect $url %{ :on-message $fn :on-close $fn :headers $map }]` | future of ws_conn | Connect to a server |
| `[http/ws-send $conn $msg]` | nil | Send a text frame |
| `[http/ws-send-binary $conn $bytes]` | nil | Send a binary frame |
| `[http/ws-ping $conn]` | nil | Send a ping frame |
| `[http/ws-close $conn ?code ?reason]` | nil | Close a connection |
| `[http/ws-on-message $conn $fn]` | nil | Register on-message callback |
| `[http/ws-on-close $conn $fn]` | nil | Register on-close callback |
| `[http/ws-stop $server]` | nil | Stop a server |
## WebSocket Server
def on-connect {|conn| @doc "A new client connected. Register handlers on the connection." echo "ws client connected" http/ws-on-message $conn { |msg| echo "received: $msg" http/ws-send $conn "echo: $msg" } http/ws-on-close $conn { echo "ws client disconnected" } } def srv [http/ws-server 8080 %{ :on-connect $on-connect }] echo "ws server on :8080" sleep 60000
## WebSocket Client
`http/ws-connect` returns a **future** that resolves to a `ws_conn`
resource once the handshake completes. Pass callbacks in the option
map to handle messages from the start, or attach them later with
`http/ws-on-message`.
def on-message {|msg| echo "got: $msg" } def on-close {|code reason| echo "closed: $code $reason" } def conn-fut [http/ws-connect "ws://echo.example.com" %{ :on-message $on-message :on-close $on-close }] def conn [await $conn-fut] http/ws-send $conn "hello" # For TLS: # def conn-fut [http/ws-connect "wss://api.example.com" %{ :on-message $on-message }]
## Frames
Three frame kinds: text, binary, ping. Send with the matching
stdlib function:
http/ws-send $conn "plain text frame" http/ws-send-binary $conn $byte-list http/ws-ping $conn # server should auto-pong
The `on-message` callback fires once per text or binary frame.
Binary frames arrive as a list of byte values; text frames arrive as
strings.
## Closing
http/ws-close $conn # default code, no reason http/ws-close $conn 1000 "bye" # explicit code and reason
Standard close codes: `1000` (normal), `1001` (going away), `1008`
(policy violation), `1011` (server error). The remote side's
`on-close` handler receives `(code, reason)`.
## Server Lifecycle
def srv [http/ws-server 8080 %{ :on-connect $on-connect }] # ... run for a while ... http/ws-stop $srv # stop accepting, drop connections
## Architecture Notes
- Each `WsConn` is heap-allocated on `std.heap.c_allocator` so it
survives the VM that started it (matches the HTTP server's
design).
- Per-fd epoll callbacks drive a state machine: handshake phases →
open → frame parser.
- Frames are parsed incrementally; one `posix.read` can yield several
frames, and one frame can span several reads.
- `wss://` client connections reuse mbedTLS via the same BIO-callback
model as `http_async.zig`. Server-side TLS is not currently
supported.
- Callbacks fire synchronously from inside the epoll callback, like
`net/on-data`.
## Frame Size Limits
| Limit | Default |
|---|---|
| Read buffer | 16 KB |
| Max frame size | 1 MB |
| Max handshake size | 64 KB |
Frames larger than 1 MB are rejected and the connection closed.
## Examples
### Broadcast server
def clients ^{} def on-connect {|conn| @doc "Track clients in a set so we can broadcast to all." $clients << $conn http/ws-on-message $conn { |msg| # Broadcast to every connected client for c in $clients { if ($c != $conn) { http/ws-send $c $msg } } } http/ws-on-close $conn { # Manual removal — sets don't support removal, so re-add all-but-this set clients [into ^{} [filter [collect $clients] { $it != $conn }]] } } def srv [http/ws-server 8080 %{ :on-connect $on-connect }] sleep 600000
### JSON RPC client
def on-message {|msg| try { set data [json/parse $msg] echo "result: $data~result" } catch _e { echo "bad json: $msg" } } def c [await [http/ws-connect "wss://rpc.example.com" %{ :on-message $on-message }]] http/ws-send $c [json/stringify %{ :jsonrpc "2.0" :id 1 :method "ping" }]
## Quick Reference
| Form | Purpose |
|---|---|
| `[http/ws-server $port %{ :on-connect $fn }]` | Start server |
| `[http/ws-connect $url %{ :on-message $fn ... }]` | Connect (returns future) |
| `[http/ws-send $conn $msg]` | Text frame |
| `[http/ws-send-binary $conn $bytes]` | Binary frame |
| `[http/ws-ping $conn]` | Ping |
| `[http/ws-close $conn ?code ?reason]` | Close |
| `[http/ws-on-message $conn $fn]` | Set message handler |
| `[http/ws-on-close $conn $fn]` | Set close handler |
| `[http/ws-stop $server]` | Stop server |