websocket:   RFC 6455 Web  Sockets
1 A first client
2 A first server
3 Messages
ws-recv
ws-message
4 API
ws-connect
ws-accept
ws-serve
ws-send-text!
ws-send-binary!
ws-ping!
ws-close!
5 Frames, if you need them
opcode?
data-opcode?
control-opcode?
frame*
frame
write-frame!
read-frame!
mask-payload
utf-8-bytes?
ws?
exn:  fail:  websocket?
6 Limitations
9.3

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

(ws-accept in out) → ws?

  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

(ws-serve listener handler) → thread?

  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

(ws-ping! conn [payload]) → void?

  conn : ws?
  payload : bytes? = #""

Sends a ping control frame (payload at most 125 bytes).

procedure

(ws-close! conn [code reason]) → void?

  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

(opcode? v) → boolean?

  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

(struct frame (fin? opcode payload masked? mask-key))

  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

(ws? v) → boolean?

  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.