sigildocs

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 payload

The 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 connection

Closing

ws-close sends a close frame (status 1000 — normal closure) and closes the underlying socket.

(ws-close conn)
(ws-connected? conn)  ; => #f

If 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

ConstantValueDescription
opcode-text#x1Text data frame
opcode-binary#x2Binary data frame
opcode-close#x8Connection close
opcode-ping#x9Ping
opcode-pong#xAPong
opcode-continuation#x0Fragment continuation
(opcode-control? opcode-ping)  ; => #t
(opcode-control? opcode-text)  ; => #f

Encoding 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 reversible

Common 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)))))