WebSocket
WebSocket client library supporting ws:// and wss:// connections (RFC 6455).
(import (sigil websocket))Connecting
ws-connect opens a WebSocket connection via the HTTP upgrade handshake. Supports both plain (ws://) and TLS (wss://) URLs.
;; Plain WebSocket
(define conn (ws-connect "ws://localhost:8080/socket"))
;; Secure WebSocket (TLS)
(define conn (ws-connect "wss://example.com/socket"))Returns a ws-connection on success, or #f if the connection or handshake fails.
Sending Messages
ws-send
Send a text message:
(ws-send conn "Hello, server!")ws-send-binary
Send binary data as a bytevector:
(ws-send-binary conn #u8(1 2 3 4))ws-ping
Send a ping frame to check connectivity:
(ws-ping conn)
(ws-ping conn "payload") ; optional payloadThe server responds with a pong frame, which is handled automatically by ws-receive.
Receiving Messages
ws-receive
Block until a complete message arrives. Returns a ws-message, the symbol 'closed if the connection was closed, or #f on error.
(let ((msg (ws-receive conn)))
(cond
((ws-message? msg)
(display (ws-message-data msg)))
((eq? msg 'closed)
(display "Connection closed"))))When running inside an async scheduler, ws-receive yields the current coroutine while waiting for data.
ws-message Accessors
(ws-message? msg) ; => #t
(ws-message-type msg) ; => 'text or 'binary
(ws-message-data msg) ; => string (text) or bytevector (binary)Dispatch on message type:
(let ((msg (ws-receive conn)))
(when (ws-message? msg)
(case (ws-message-type msg)
((text) (display (ws-message-data msg)))
((binary) (process-bytes (ws-message-data msg))))))Connection State
(ws-connection? conn) ; => #t (type predicate)
(ws-connected? conn) ; => #t (state is 'open)
(ws-connection-state conn) ; => 'connecting, 'open, 'closing, or 'closed
(ws-connection-url conn) ; => "ws://localhost:8080/socket"
(ws-connection-socket conn) ; => underlying TCP socket or TLS connectionClosing
ws-close sends a close frame (status 1000 — normal closure) and closes the underlying socket.
(ws-close conn)
(ws-connected? conn) ; => #fIf the server initiates a close, ws-receive returns 'closed and the close handshake is completed automatically.
Frame API
The (sigil websocket frame) module provides low-level frame encoding and decoding for advanced use cases.
(import (sigil websocket frame))Opcodes
| Constant | Value | Description |
|---|---|---|
opcode-text | #x1 | Text data frame |
opcode-binary | #x2 | Binary data frame |
opcode-close | #x8 | Connection close |
opcode-ping | #x9 | Ping |
opcode-pong | #xA | Pong |
opcode-continuation | #x0 | Fragment continuation |
(opcode-control? opcode-ping) ; => #t
(opcode-control? opcode-text) ; => #fEncoding Frames
All client frames are masked per RFC 6455.
(encode-text-frame "hello") ; => bytevector
(encode-binary-frame #u8(1 2 3)) ; => bytevector
(encode-close-frame) ; => bytevector (no status)
(encode-close-frame 1000) ; => bytevector (normal closure)
(encode-ping-frame) ; => bytevector
(encode-pong-frame payload-bv) ; => bytevector
;; General-purpose: (encode-frame opcode payload fin? mask?)
(encode-frame opcode-text "data" #t #t)Decoding Frames
(let ((result (decode-frame bytevector-data)))
(when (frame-decode-result-frame result)
(let ((frame (frame-decode-result-frame result))
(consumed (frame-decode-result-bytes-consumed result)))
(display (ws-frame-opcode frame))
(display (ws-frame-payload frame))
(display (ws-frame-fin? frame)))))Returns a frame-decode-result with #f for the frame if the data is incomplete.
Masking
(define key (generate-mask-key)) ; => 4-byte bytevector
(define masked (apply-mask data key))
(define original (apply-mask masked key)) ; XOR is reversibleCommon Patterns
Echo Client
(import (sigil websocket))
(let ((conn (ws-connect "ws://localhost:8080/echo")))
(when conn
(ws-send conn "Hello!")
(let ((msg (ws-receive conn)))
(when (ws-message? msg)
(display (ws-message-data msg)))) ; => "Hello!"
(ws-close conn)))JSON Message Exchange
(import (sigil websocket)
(sigil json))
(let ((conn (ws-connect "ws://localhost:8080/api")))
(when conn
;; Send JSON
(ws-send conn (json->string '((action . "subscribe")
(channel . "updates"))))
;; Receive JSON
(let ((msg (ws-receive conn)))
(when (ws-message? msg)
(let ((data (string->json (ws-message-data msg))))
(display (assoc-ref 'status data)))))
(ws-close conn)))Reconnection Loop
(import (sigil websocket))
(define (connect-with-retry url max-attempts)
(let loop ((attempt 1))
(let ((conn (ws-connect url)))
(cond
(conn conn)
((< attempt max-attempts)
(display (string-append "Retry " (number->string attempt) "...\n"))
(sleep 1)
(loop (+ attempt 1)))
(else #f)))))