websocket: RFC 6455 WebSockets
| (require websocket) | package: websocket |
This package implements the RFC 6455 WebSocket protocol, both client and server side. It is a protocol library, not a framework: connections become Racket-port-shaped objects and everything else — routing, sessions, serialization — stays with the caller. It composes with tcp-listen / tcp-accept on the server side and ws:// / wss:// URLs on the client side.
1 A first client
> (require websocket) > (define conn (ws-connect "ws://localhost:8080/chat")) > (ws-send-text! conn "hello") > (define msg (ws-recv conn)) > (ws-close! conn)
2 A first server
A server accepts raw TCP connections and upgrades them. ws-serve wires the accept loop and one handler thread per connection; for integration with an existing event loop, call ws-accept yourself on the two directions of a tcp-accept result.
> (require websocket racket/tcp) > (define listener (tcp-listen 8080 16 #t "127.0.0.1")) > (ws-serve listener (λ (conn) (ws-send-text! conn "welcome")))
3 Messages
procedure
(ws-recv conn) → (or/c ws-message? eof-object?)
conn : ws?
Blocks for the next complete message. Control frames are handled transparently: pings are answered with pongs, pongs are consumed. Once the peer closes (or closes back after us), eof is returned. Protocol violations raise exn:fail:websocket? after sending the appropriate close frame (1002 protocol error, 1007 invalid UTF-8).
struct
(struct ws-message (type payload))
type : (or/c 'text 'binary) payload : bytes?
4 API
procedure
(ws-connect url [#:headers headers]) → ws?
url : string? headers : (listof (cons/c string? string?)) = '()
Opens a ws:// or wss:// connection (plain TCP or TLS), performs the opening handshake, and returns the connection. Additional handshake headers can be supplied via headers. Every handshake failure — rejection, malformed response, or a connection reset mid-handshake — raises exn:fail:websocket?.
procedure
in : input-port? out : output-port?
Performs the server side of the opening handshake on an already accepted TCP connection and returns the connection.
procedure
listener : tcp-listener? handler : (-> ws? any/c)
Runs an accept loop on listener in a fresh thread, upgrading each connection and running handler with it in its own thread. The loop exits when the listener is closed.
procedure
(ws-send-text! conn text) → void?
conn : ws? text : string?
procedure
(ws-send-binary! conn payload) → void?
conn : ws? payload : bytes?
Send one complete data message. Writes are serialized across threads.
procedure
conn : ws? payload : bytes? = #""
Sends a ping control frame (payload at most 125 bytes).
procedure
conn : ws? code : exact-nonnegative-integer? = 1000 reason : string? = ""
Sends a close frame (once) and closes the ports. Idempotent.
5 Frames, if you need them
The underlying RFC 6455 section 5 frame codec is exposed for proxies, tests, and unusual plumbing. Frame payloads are always plaintext; the wire representation (frame-masked?, frame-mask-key) is carried alongside.
procedure
v : any/c
Opcode symbols: 'continuation, 'text, 'binary, 'close, 'ping, 'pong.
procedure
(data-opcode? v) → boolean?
v : any/c
procedure
(control-opcode? v) → boolean?
v : any/c
procedure
(frame* #:opcode opcode #:payload payload [ #:fin? fin? #:masked? masked? #:mask-key mask-key]) → frame? opcode : opcode? payload : bytes? fin? : boolean? = #t masked? : boolean? = #f mask-key : (or/c bytes? #f) = #f
Constructs a frame. When masked? is set without a mask-key, write-frame! generates a fresh random key.
struct
fin? : boolean? opcode : opcode? payload : bytes? masked? : boolean? mask-key : (or/c bytes? #f)
procedure
(write-frame! out f) → void?
out : output-port? f : frame?
Encodes and writes one frame. Rejects invalid control frames.
procedure
(read-frame! in [#:role role]) → (or/c frame? eof-object?)
in : input-port? role : (or/c 'server 'client 'any) = 'any
Reads one frame from in, returning eof at a clean connection boundary. With #:role 'server unmasked client frames are rejected; with #:role 'client masked server frames are rejected. RSV bits, reserved opcodes, and invalid control frames raise exn:fail:websocket?.
procedure
(mask-payload payload key) → bytes?
payload : bytes? key : (or/c bytes? #f)
XOR-applies the 4-byte key to payload; since masking is self-inverse this both masks and unmasks. A #f key returns the payload unchanged.
procedure
(utf-8-bytes? b) → boolean?
b : bytes?
RFC 3629 validation: rejects stray continuation bytes, overlong encodings, UTF-16 surrogates, and anything past U+10FFFF.
procedure
v : any/c
Predicate for connection objects.
procedure
(exn:fail:websocket? v) → boolean?
v : any/c
Raised for handshake failures and protocol violations.
6 Limitations
No permessage-deflate compression extension. No background keepalive thread — pings are answered while you are in ws-recv; run a sender thread that calls ws-ping! periodically if you need liveness. The 63-bit length cap of the RFC applies.