AtlatestRepositorysigil-websocket
1# WebSocket
2
3> WebSocket client library supporting ws:// and wss:// connections (RFC 6455).
4
5```scheme
6(import (sigil websocket))
7```
8
9## Connecting
11`ws-connect` opens a WebSocket connection via the HTTP upgrade handshake. Supports both plain (`ws://`) and TLS (`wss://`) URLs.
13```scheme
14;; Plain WebSocket
15(define conn (ws-connect "ws://localhost:8080/socket"))
17;; Secure WebSocket (TLS)
18(define conn (ws-connect "wss://example.com/socket"))
19```
21Returns a `ws-connection` on success, or `#f` if the connection or handshake fails.
23## Sending Messages
25### ws-send
27Send a text message:
29```scheme
30(ws-send conn "Hello, server!")
31```
33### ws-send-binary
35Send binary data as a bytevector:
37```scheme
38(ws-send-binary conn #u8(1 2 3 4))
39```
41### ws-ping
43Send a ping frame to check connectivity:
45```scheme
46(ws-ping conn)
47(ws-ping conn "payload") ; optional payload
48```
50The server responds with a pong frame, which is handled automatically by `ws-receive`.
52## Receiving Messages
54### ws-receive
56Block until a complete message arrives. Returns a `ws-message`, the symbol `'closed` if the connection was closed, or `#f` on error.
58```scheme
59(let ((msg (ws-receive conn)))
60 (cond
61 ((ws-message? msg)
62 (display (ws-message-data msg)))
63 ((eq? msg 'closed)
64 (display "Connection closed"))))
65```
67When running inside an async scheduler, `ws-receive` yields the current coroutine while waiting for data.
69### ws-message Accessors
71```scheme
72(ws-message? msg) ; => #t
73(ws-message-type msg) ; => 'text or 'binary
74(ws-message-data msg) ; => string (text) or bytevector (binary)
75```
77Dispatch on message type:
79```scheme
80(let ((msg (ws-receive conn)))
81 (when (ws-message? msg)
82 (case (ws-message-type msg)
83 ((text) (display (ws-message-data msg)))
84 ((binary) (process-bytes (ws-message-data msg))))))
85```
87## Connection State
89```scheme
90(ws-connection? conn) ; => #t (type predicate)
91(ws-connected? conn) ; => #t (state is 'open)
92(ws-connection-state conn) ; => 'connecting, 'open, 'closing, or 'closed
93(ws-connection-url conn) ; => "ws://localhost:8080/socket"
94(ws-connection-socket conn) ; => underlying TCP socket or TLS connection
95```
97## Closing
99`ws-close` sends a close frame (status 1000 — normal closure) and closes the underlying socket.
101```scheme
102(ws-close conn)
103(ws-connected? conn) ; => #f
104```
106If the server initiates a close, `ws-receive` returns `'closed` and the close handshake is completed automatically.
108## Frame API
110The `(sigil websocket frame)` module provides low-level frame encoding and decoding for advanced use cases.
112```scheme
113(import (sigil websocket frame))
114```
116### Opcodes
118| Constant | Value | Description |
119|----------|-------|-------------|
120| `opcode-text` | `#x1` | Text data frame |
121| `opcode-binary` | `#x2` | Binary data frame |
122| `opcode-close` | `#x8` | Connection close |
123| `opcode-ping` | `#x9` | Ping |
124| `opcode-pong` | `#xA` | Pong |
125| `opcode-continuation` | `#x0` | Fragment continuation |
127```scheme
128(opcode-control? opcode-ping) ; => #t
129(opcode-control? opcode-text) ; => #f
130```
132### Encoding Frames
134All client frames are masked per RFC 6455.
136```scheme
137(encode-text-frame "hello") ; => bytevector
138(encode-binary-frame #u8(1 2 3)) ; => bytevector
139(encode-close-frame) ; => bytevector (no status)
140(encode-close-frame 1000) ; => bytevector (normal closure)
141(encode-ping-frame) ; => bytevector
142(encode-pong-frame payload-bv) ; => bytevector
144;; General-purpose: (encode-frame opcode payload fin? mask?)
145(encode-frame opcode-text "data" #t #t)
146```
148### Decoding Frames
150```scheme
151(let ((result (decode-frame bytevector-data)))
152 (when (frame-decode-result-frame result)
153 (let ((frame (frame-decode-result-frame result))
154 (consumed (frame-decode-result-bytes-consumed result)))
155 (display (ws-frame-opcode frame))
156 (display (ws-frame-payload frame))
157 (display (ws-frame-fin? frame)))))
158```
160Returns a `frame-decode-result` with `#f` for the frame if the data is incomplete.
162### Masking
164```scheme
165(define key (generate-mask-key)) ; => 4-byte bytevector
166(define masked (apply-mask data key))
167(define original (apply-mask masked key)) ; XOR is reversible
168```
170## Common Patterns
172### Echo Client
174```scheme
175(import (sigil websocket))
177(let ((conn (ws-connect "ws://localhost:8080/echo")))
178 (when conn
179 (ws-send conn "Hello!")
180 (let ((msg (ws-receive conn)))
181 (when (ws-message? msg)
182 (display (ws-message-data msg)))) ; => "Hello!"
183 (ws-close conn)))
184```
186### JSON Message Exchange
188```scheme
189(import (sigil websocket)
190 (sigil json))
192(let ((conn (ws-connect "ws://localhost:8080/api")))
193 (when conn
194 ;; Send JSON
195 (ws-send conn (json->string '((action . "subscribe")
196 (channel . "updates"))))
197 ;; Receive JSON
198 (let ((msg (ws-receive conn)))
199 (when (ws-message? msg)
200 (let ((data (string->json (ws-message-data msg))))
201 (display (assoc-ref 'status data)))))
202 (ws-close conn)))
203```
205### Reconnection Loop
207```scheme
208(import (sigil websocket))
210(define (connect-with-retry url max-attempts)
211 (let loop ((attempt 1))
212 (let ((conn (ws-connect url)))
213 (cond
214 (conn conn)
215 ((< attempt max-attempts)
216 (display (string-append "Retry " (number->string attempt) "...\n"))
217 (sleep 1)
218 (loop (+ attempt 1)))
219 (else #f)))))
220```