RFC 6455 Web  Sockets for Racket
1 Introduction
2 Synopsis
3 License
4 API
ws-url?
wss-url?
ws-conn?
ws-conn-supports-fragmentation?
ws-conn-supports-payload-type?
ws-conn-signals-status-on-close?
ws-conn-closed?
ws-connect
ws-serve
ws-serve*
ws-service-mapper
ws-send!
ws-recv
ws-close!
rfc6455-stream-buffer-size
hybi00-framing-mode
ws-idle-timeout
7.0

RFC 6455 WebSockets for Racket

Tony Garnock-Jones <[email protected]>

    1 Introduction

    2 Synopsis

    3 License

    4 API

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

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

(ws-url? x)  boolean?

  x : any/c
Returns true if and only if x is a url? and has a url-scheme equal to "ws" or "wss".

procedure

(wss-url? x)  boolean?

  x : any/c
Returns true if and only if x is a url? and has a url-scheme equal to "wss".

procedure

(ws-conn? x)  boolean?

  x : any/c
Returns #t if and only if x is a WebSocket connection as defined by this package.

#t iff the connected peer supports sending and receiving fragmented/streamed messages. Currently, RFC 6455-compliant peers support fragmentation, while hybi-00 peers do not.

procedure

(ws-conn-supports-payload-type? c    
  payload-type)  boolean?
  c : ws-conn?
  payload-type : symbol?
#t iff the connected peer supports the requested payload type. RFC 6455-compliant peers support types 'text and 'binary, while hybi-00 peers support only 'text.

#t iff the status and/or reason values are communicated to the remote peer on connection close (ws-close!). RFC 6455 includes space in the wire protocol for these values, while hybi-00 does not.

procedure

(ws-conn-closed? c)  boolean?

  c : ws-conn?
Returns #t if the given connection has been closed, and #f otherwise.

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
Connect to the given WebSockets server, via TLS if (wss-url? u). Supplies headers as additional headers in the WebSocket handshake request. A protocol variant other than the RFC 6455 standard can be chosen by supplying a value for the #:protocol parameter.

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)))
This is a convenience entry point, largely directly compatible with ws-serve from the built-in websocket support in older versions of Racket, including all its arguments besides those shown. If #:conn-headers is supplied, then it is inspected to see whether it takes two or three arguments, and is called appropriately.

procedure

(ws-serve* service-mapper ...)  (-> void)

  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
Macro that expands to an expression suitable for use as a service-mapper with ws-serve*.

Each uri-regexp is matched against an incoming request’s URL in turn until one matches. Then,

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
Sends a message to the remote peer.

(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.

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
Receives a message from the remote peer.

(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? = ""
Closes a connection. Has no effect if the connection is already closed. The status code and reason are supplied to the remote peer in the close frame.

(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?.)

Used when streaming the contents of an input-port given to ws-send!. Streamed message fragments will be no larger than (rfc6455-stream-buffer-size) bytes each.

parameter

(hybi00-framing-mode)  (or/c 'new 'old)

(hybi00-framing-mode mode)  void?
  mode : (or/c 'new 'old)
Used with pre-hybi-00 peers. Selects either the "new" or "old" framing modes. You only have to worry about this if you’re trying to communicate with truly ancient, pre-hybi-00 peers, and then you no doubt have bigger problems.

parameter

(ws-idle-timeout)  number?

(ws-idle-timeout seconds)  void?
  seconds : number?
Idle timeout in seconds. If the interval between successive received frames (of any type) exceeds this number of seconds, the connection will be closed.

This parameter defaults to 300 seconds, i.e. five minutes.