(sigil websocket connection)
(sigil websocket connection) - WebSocket Connection Management
Handles WebSocket connections with handshake, frame I/O, and state management. Supports both ws:// (plain) and wss:// (TLS) connections.
Exports
ws-connectionprocedureConstruct a ws-connection struct.
ws-connection?procedureTest if a value is a ws-connection struct.
ws-connection-socketprocedureGet the socket field of a ws-connection struct.
ws-connection-stateprocedureGet the state field of a ws-connection struct.
ws-connection-urlprocedureGet the url field of a ws-connection struct.
ws-connection-hostprocedureGet the host field of a ws-connection struct.
ws-connection-bufferprocedureGet the buffer field of a ws-connection struct.
ws-connection-fragmentsprocedureGet the fragments field of a ws-connection struct.
ws-connection-non-blockingprocedureGet the non-blocking field of a ws-connection struct.
set-ws-connection-state!procedureSet the state field of a ws-connection struct.
set-ws-connection-buffer!procedureSet the buffer field of a ws-connection struct.
set-ws-connection-fragments!procedureSet the fragments field of a ws-connection struct.
set-ws-connection-non-blocking!procedureSet the non-blocking field of a ws-connection struct.
%ws-message--typevariableSet the non-blocking field of a ws-connection struct.
ws-messageprocedureConstruct a ws-message struct.
ws-message?procedureTest if a value is a ws-message struct.
ws-message-typeprocedureGet the type field of a ws-message struct.
ws-message-dataprocedureGet the data field of a ws-message struct.
parse-ws-urlprocedureParse WebSocket URL into components Returns (scheme host port path) or #f on error
generate-ws-keyprocedureGenerate random 16-byte key and base64 encode
calculate-accept-keyprocedureCalculate expected Sec-WebSocket-Accept value
build-handshake-requestprocedureBuild HTTP upgrade request
parse-status-lineprocedureParse HTTP response status line Returns status code or #f
get-headerprocedureExtract header value (case-insensitive)
find-http-header-endprocedureFind the first HTTP header terminator in a bytevector. The response can share a socket read with the first WebSocket frame, whose arbitrary binary payload must never be decoded as UTF-8 while locating the marker.
validate-handshakeprocedureRead HTTP response and validate handshake Returns #t on success, #f on failure
conn-writeprocedureWrite one complete WebSocket frame. Both plain and TLS sockets may accept only a prefix when nonblocking. Keep the encoded (and masked) bytes intact and resume at the committed offset after backpressure.
conn-readprocedureRead from socket (handles both TCP and TLS).
Returns a bytevector for plain TCP so binary WebSocket frame bytes round-trip cleanly. The legacy socket-read path returns a string and re-encodes any non-ASCII bytes through UTF-8, corrupting frame headers (the high opcode byte 0x81 in particular). TLS still goes through tls-read which handles raw bytes natively.
conn-closeprocedureClose socket (handles both TCP and TLS)
conn-set-non-blocking!procedureSet socket to non-blocking mode (handles both TCP and TLS)
ws-connectvariableConnect to a WebSocket server url: WebSocket URL (ws:// or wss://) Returns ws-connection on success, #f on failure
ws-connected?procedureCheck if connection is open
ws-sendprocedureSend a text message
ws-send-binaryprocedureSend a binary message
ws-pingprocedureSend a ping frame
ws-closeprocedureClose the WebSocket connection
receive-messageprocedureInternal: Read data and try to decode a message Returns ws-message, 'closed, 'need-more, or #f on error
try-decode-messageprocedureTry to decode a complete message from buffer Returns ws-message, 'closed, 'need-more, or #f on error
process-frameprocedureProcess a decoded frame Returns ws-message, 'closed, 'need-more, or #f on error
ws-receiveprocedureReceive a message (with async I/O support) Returns ws-message, 'closed, or #f on error When running in async scheduler, yields while waiting for data.
ws-receive-nowaitprocedureReceive a message that is ALREADY BUFFERED, without ever waiting. Returns ws-message, 'closed, or #f when nothing complete is buffered.
WHY THIS EXISTS. ws-receive consults the connection buffer only INSIDE the call, and when that buffer holds no complete frame it waits (await-readable, no timeout). So a caller that drives several things from one event pump cannot ask "do you already have a frame?" without risking a park. That matters because one socket read routinely yields SEVERAL complete frames: after the first is returned, the rest sit in the buffer with nothing further on the socket. A pump that gates ws-receive on socket readability — the only way to keep it from parking — then strands those frames until unrelated bytes happen to arrive. Measured against this library at 0.10.4: two 13-byte frames written in one segment followed by three seconds of silence, and the second frame surfaced 2997 ms late, with the wake frame.
CALL IT ALONE, ON A LOOP — BUT ONLY IF THE CONNECTION WAS OPENED SO THAT ITS TRANSPORT IS NON-BLOCKING. That is the whole precondition, and it is not a formality:
(ws-connect url upgrade-timeout-ms: 5000) ; non-blocking -> self-sufficient (with-async … (ws-connect url) …) ; non-blocking -> self-sufficient (ws-connect url) ; BLOCKING -> BUFFER-ONLY, DEAF
ws-connect puts the transport in non-blocking mode when a scheduler is running OR an upgrade-timeout-ms was supplied. With NEITHER, the socket stays blocking, this procedure cannot take its socket read without risking a park, so it correctly declines to — and a pump that calls it alone will never hear anything. Pass a timeout, or run under a scheduler, or do not use a nowait-only pump.
Given that precondition: it first decodes anything already buffered, then takes exactly one socket read. A pump needs nothing else, and needs no scheduler.
DO NOT pair it with socket-ready?. An earlier revision of this docstring prescribed
(or (ws-receive-nowait conn) (and (socket-ready? sock) (ws-receive conn)))
and that advice is unwritable over wss://: socket-ready? raises socket-ready?: expected socket on a TLS connection, because its native rejects anything that is not a plain socket instead of resolving the fd the way socket-select does. socket-select accepts both and is the primitive to reach for if you ever do need an external readiness gate. The two disagree about the same object; see the sigil-lib note on native_socket_readyp.
It is also the wrong shape for a no-scheduler caller for a second reason: ws-receive only yields via await-readable when a scheduler is present, so on a partial frame without one it spins.
THE RETURN CONTRACT, and the distinction is load-bearing:
#f nothing to give you RIGHT NOW — an empty buffer, a partial frame, or a connection not yet promoted to 'open. 'closed affirmatively closed, and ONLY that.
An absent answer and a negative answer must not share a value. A first draft mirrored ws-receive and returned 'closed for anything that was not ws-connected? — which REPORTS A FRESH SOCKET AS DEAD, because the state is not promoted to 'open until a receive poll has run. ws-receive can get away with that conflation only because it blocks afterwards and the window never shows; a non-blocking caller sees it immediately and tears down a healthy connection. Consumers therefore act on 'closed without needing to know anything about promotion order.