RFC 6455 WebSockets for Racket
1 Introduction
This package, rfc6455, provides RFC 6455 compatible WebSockets server and client interfaces for Racket, building on Racket’s web-server collection.
Besides support for RFC 6455, the final WebSockets standard, the package also incorporates code supporting the earlier, draft hybi-00 proposal, because several common older browsers still use this protocol variant.
Wikipedia has a good section on browser support for WebSocket protocol variations, which states "All the latest browsers except Android browser support the latest specification (RFC 6455) of the WebSocket protocol."
This package has been developed against
Firefox 24.0 (which is an RFC 6455 peer)
Chrome 30.0.1599.101 (which is an RFC 6455 peer)
Safari 5.1.10 (which is a hybi-00 peer)
2 Synopsis
Using the legacy net/websocket-compatible interface:
(require net/rfc6455) (ws-serve #:port 8081 (lambda (c s) (ws-send! c "Hello world!")))
Using an interface that supports URL path matching and WebSocket subprotocol selection:
(require net/rfc6455) (ws-serve* #:port 8081 (ws-service-mapper ["/test" ; the URL path (regular expression) [(subprotocol) ; if client requests subprotocol "subprotocol" (lambda (c) (ws-send! c "You requested a subprotocol"))] [(#f) ; if client did not request any subprotocol (lambda (c) (ws-send! c "You didn't explicitly request a subprotocol"))]]))
Creating a client connection:
(require net/rfc6455) (require net/url) (define c (ws-connect (string->url "ws://localhost:8081/"))) (ws-send! c "Hello world!")
3 License
All the code in this package is licensed under the LGPL, version 3.0 or any later version. Each source file has a brief copyright and licensing notice attached, but see the licence text itself for full details.
The only exceptions to the above are the files marked "public domain" in the net/rfc6455/examples directory. They are intended to be examples of usage of the package for people to build on without concern for licensing minutiae.
4 API
(require net/rfc6455) | package: rfc6455 |
The interface is based on the net/websocket API from older versions of Racket, with some extensions and differences.
procedure
c : ws-conn?
procedure
(ws-conn-supports-payload-type? c payload-type) → boolean? c : ws-conn? payload-type : symbol?
procedure
c : ws-conn?
procedure
(ws-conn-closed? c) → boolean?
c : ws-conn?
procedure
(ws-connect u [ #:headers headers #:protocol protocol]) → ws-conn? u : (or/c ws-url? wss-url?) headers : (listof header?) = '() protocol : (or/c 'rfc6455 'hybi00) = 'rfc6455
procedure
(ws-serve conn-dispatch #:conn-headers conn-headers ...) → (-> void) conn-dispatch : (-> ws-conn? request? void)
conn-headers :
(or/c (-> bytes? (listof header?) request? (values (listof header?) any/c)) (-> bytes? (listof header?) (values (listof header?) any/c)))
procedure
service-mapper :
(-> url? (-> (or/c symbol? #f) (or/c #f (-> ws-conn? void))))
Like ws-serve, except uses the given service-mapper to decide how to handle an incoming request. See ws-service-mapper.
syntax
(ws-service-mapper [uri-regexp [(protocol ...) function-expr] ...] ...)
protocol = symbol | #f
Each uri-regexp is matched against an incoming request’s URL in turn until one matches. Then,
if the client supplied a Sec-WebSocket-Protocol header, each token from that header is checked against the protocols in turn. If one matches, the corresponding function-expr is used as the connection handler; or,
if no such header was supplied, the first function-expr with a literal #f among its protocols is used.
The function-exprs must evaluate to connection handler procedures, each taking a ws-conn? as their only argument.
procedure
(ws-send! c payload [ #:final-fragment? final-fragment? #:payload-type payload-type #:flush? flush?]) → void? c : ws-conn? payload : (or/c string? bytes? input-port?) final-fragment? : boolean? = #t payload-type : (or/c 'continuation 'text 'binary) = 'text flush? : boolean? = #t
(Note: Only RFC 6455 peers support fragmentation and non-text payloads. Attempts to use these features with hybi-00 peers will signal an error. See ws-conn-supports-fragmentation? and ws-conn-supports-payload-type?.)
If payload is a string, it is converted to bytes using string->bytes/utf-8 before transmission. If it is an input-port, it is read from and streamed using multiple WebSockets message fragments to the peer until it yields eof (see also rfc6455-stream-buffer-size).
If flush? is false, the buffers of the underlying connection socket output-ports are not flushed after sending the message. Otherwise (i.e. by default), they are flushed.
If payload-type is 'text or 'binary, the appropriate WebSockets content type bit is set upon transmission.
Fragmented messages can be sent using this procedure.
The first fragment in a sequence must have payload-type set to 'text or 'binary. Every subsequent fragment in the same sequence must have payload-type set to 'continuation.
The final fragment in a sequence must have final-fragment? set to a non-false value. Every other fragment in a sequence must have final-fragment? set to #f.
For single-fragment (unfragmented) messages, the defaults are fine: a plain (ws-send! c payload) is enough. Here is an example of a multi-fragment message:
(ws-send! c #"first" #:final-fragment? #f) (ws-send! c #"second" #:final-fragment? #f #:payload-type 'continuation) (ws-send! c #"third" #:final-fragment? #t #:payload-type 'continuation)
procedure
(ws-recv c [ #:stream? stream? #:payload-type payload-type]) → (or/c eof-object? string? bytes? input-port?) c : ws-conn? stream? : boolean? = #f payload-type : (or/c 'auto 'text 'binary) = 'auto
(Note: Only RFC 6455 peers support streaming and non-text payloads. Attempts to use these features with hybi-00 peers will signal an error. See ws-conn-supports-fragmentation? and ws-conn-supports-payload-type?.)
If stream? is true, returns an input port from which the bytes or characters making up the message can be read. An end-of-file from the resulting input port is ambiguous: it does not separate the end of the message being read from the end of the connection itself. Use ws-conn-closed? to disambiguate.
If stream? is #f, returns either a string or a bytes, depending on payload-type. If a specific 'text or 'binary payload type is requested, the corresponding result type is returned, or if 'auto (the default) is requested, the message’s own text/binary indicator bit is used to decide which to return. If eof occurs mid-message, fragments so far received are discarded and eof is returned.
Multi-fragment messages are transparently reassembled: in the case of a returned input-port, fragment boundaries are not preserved, and in the case of a returned string or bytes, the entire reassembled message is returned.
procedure
(ws-close! c [ #:status status #:reason reason]) → void? c : ws-conn? status : integer? = 1000 reason : string? = ""
(Note: hybi-00 peers do not have room in their wire protocol for the status and reason codes. See ws-conn-signals-status-on-close?.)
parameter
(rfc6455-stream-buffer-size size) → void? size : integer?
parameter
(hybi00-framing-mode) → (or/c 'new 'old)
(hybi00-framing-mode mode) → void? mode : (or/c 'new 'old)
parameter
(ws-idle-timeout seconds) → void? seconds : number?
This parameter defaults to 300 seconds, i.e. five minutes.