websocket - RFC 6455 WebSocket client
Import with import "websocket.j" as websocket;. Open a WebSocket connection,
send and receive text / binary messages, and close it - a full RFC 6455 client
over net. connect performs the HTTP Upgrade
handshake and verifies the server’s Sec-WebSocket-Accept (SHA-1 + base64
of the client key); send / sendBytes write masked frames (client-to-server
frames must be masked); receive reads the next message, answering pings with
pongs and reassembling fragmented messages. Needs the default jennifer binary.
A protocol error or dropped connection throws Error{kind: "websocket"}.
import "websocket.j" as websocket;
def ws as websocket.Conn init websocket.connect("wss://ws.postman-echo.com/raw");
websocket.send($ws, "hello");
def m as websocket.Message init websocket.receive($ws); # m.kind "text", m.text "hello"
websocket.close($ws);
Runnable: examples/modules/websocket_demo.j.
Connecting
ws:// is a plain TCP connection; wss:// is TLS (net.connectTLS). The port
defaults to 80 / 443 and may be overridden in the URL (ws://host:9001/path).
| Call | Returns | |
|---|---|---|
websocket.connect(url) | Conn | handshake with the default receive timeout (30 s) |
websocket.connectWith(url, timeoutMs) | Conn | handshake with an explicit per-receive timeout |
def struct websocket.Conn { socket as net.Conn, timeoutMs as int };
The handshake sends a random 16-byte Sec-WebSocket-Key and rejects the
connection (throws) unless the response is 101 and its
Sec-WebSocket-Accept matches base64(SHA1(key + GUID)).
Sending and receiving
| Call | Returns | |
|---|---|---|
websocket.send(c, text) | send a text message | |
websocket.sendBytes(c, data) | send a binary message | |
websocket.ping(c) | send a ping | |
websocket.receive(c) | Message | read the next message |
websocket.close(c) | send a close frame and shut the socket |
Outgoing frames are masked with a fresh key (as the spec requires) and length-
encoded in the 7-bit, 16-bit, or 64-bit form automatically. receive returns a
Message:
def struct websocket.Message {
kind as string, # "text", "binary", "close", or "pong"
text as string, # decoded text (for a "text" message; "" otherwise)
data as bytes # the raw payload bytes
};
receive transparently answers a ping with a pong and keeps reading, and
reassembles a fragmented message (continuation frames) into one Message. A
close frame surfaces as kind == "close" - stop reading and close the
connection. Each receive is bounded by the connection’s timeoutMs; a timeout
throws.
def m as websocket.Message init websocket.receive($ws);
if ($m.kind == "close") {
websocket.close($ws);
} elseif ($m.kind == "text") {
# handle $m.text
}
Scope
- Client only. A server-side upgrade would need an
httpdconnection-hijack hook (a separate, larger piece). - Non-crypto masking key / nonce. The 4-byte mask and the handshake nonce
come from
math’s non-crypto RNG - neither is a security boundary (masking defeats proxy cache poisoning; the nonce only needs to rarely repeat), so this is correct, not acryptogap. - No permessage-deflate / extensions, no subprotocol negotiation. The
Sec-WebSocket-Protocol/-Extensionsheaders are not sent. receiveblocks per message up totimeoutMs; there is no non-blocking poll (drive one message at a time, or runreceivein aspawn).
See also
- net.md - the TCP / TLS transport this is built on.
- http.md - the HTTP/1.1 client (a different, request/response
protocol over the same
net). - modules/index.md - the module catalog and import rules.