chickadee » ws-client

ws-client

Description

An implementation of the client side of the WebSocket Protocol (RFC6455), including the permessage-deflate extension (RFC7692). It passes all of the Autobahn Testsuite compliance tests.

Requirements

External dependencies:

Uses tcp6 if it is imported together with this extension.

API

Establishing a WebSocket connection
ws-connectionrecord
(ws-connect STRING #!optional (list WS-EXTENSION ...) (list SYMBOL ...)) -> (or WS-CONNECTION RECORD)procedure

The state of a WebSocket connection is stored in a record of type ws-connection.

The procedure ws-connect attempts to establish a WebSocket connection to a server. The argument STRING should be a valid WebSocket URI.

If the opening handshake for the WebSocket protocol succeeds, this procedure returns a ws-connection record. If it receives a HTTP response from the server with status code other than 101, it instead returns a record containing the HTTP response (see intarweb). The user should call is-ws-connection? on the result of ws-connect to check if this has happened and, in the case of an unsuccessful handshake, process the HTTP response accordingly, e.g. by following a redirect.

To use WebSockets over TLS, supply a URI with scheme wss.

The procedure ws-connect optionally accepts a list of extensions the client hopes to use. The only extension currently supported by this library is permessage-deflate; see the relevant section.

The second optional argument is a list of flags (symbols). The only one currently supported is 'strip-host, which strips the hostname, port, and scheme from the method line of the client handshake. Some non-compliant implementations seem to require this.

Sending and receiving messages
ws-messagerecord
message-type WS-MESSAGEprocedure
message-data* WS-MESSAGEprocedure
message-frames WS-MESSAGEprocedure

WebSocket messages are represented with the record type ws-message.

For a message m, The procedure (message-type m) yields the type of m as specified by the opcode of its first frame, which is either 'text or 'binary.

If m is a message obtained with recv-message, (message-frames m) is a list of frames from which m has been assembled, after any per-frame transforms defined by extensions have been applied. In the absence of extensions which apply per-message transforms, (message-data* m) is the concatenation of the contents of each (frame-payload-data f) for f in (message-frames m).

message-size WS-MESSAGEprocedure

Equivalent to (u8vector-length (message-data* WS-MESSAGE)).

message-data WS-MESSAGEprocedure

The procedure (message-data m) yields (message-data* m) converted to the corresponding type depending on (message-type m).

recv-message WS-CONNECTIONprocedure

Receives a message from the server. The procedure quietly processes any control frames encountered while assembling the message.

If it encounters a connection-close frame, it closes the connection and returns #f instead. Once this has happened, the application should not invoke recv-message again on the same connection.

send-message WS-CONNECTION WS-MESSAGEprocedure

Sends a message to the server. Currently only useful for echoing received messages; see instead send-text-message and send-binary-message below.

send-text-message WS-CONNECTION STRINGprocedure
send-binary-message WS-CONNECTION BLOBprocedure

Sends a text (resp. binary) message to (resp. from) the server.

recv-message-loop WS-CONNECTION HANDLERprocedure

Receives messages from the server and calls the procedure HANDLER on each received message, until the connection is closed. The procedure responds to (or sends, e.g. in the case of closure due to a protocol error) connection close frames so that the connection is closed cleanly.

Closing a WebSocket connection
ws-close WS-CONNECTION SYMBOLprocedure

Sends a frame with optype 'connection-close to the server, with payload the close code corresponding to the reason given by SYMBOL (see reason->close-code).

Once the application has called ws-close with a connection, it should not send any further data through the connection, though it may still receive messages until the server sends a connection close frame in return.

Low-level interface

An interface is provided for when an application wishes to interact with the connection on the level of individual WebSocket frames.

Care should be taken if these procedures are used in conjunction with the message-level interface: for example, if the first frame of a fragmented message has been consumed using recv-frame, invoking recv-message results in an error on the next frame since there is nothing to continue.

ws-framerecord
frame-fin WS-FRAMEprocedure
frame-rsv WS-FRAMEprocedure
frame-opcode WS-FRAMEprocedure
frame-optype WS-FRAMEprocedure
frame-mask? WS-FRAMEprocedure
frame-payload-length WS-FRAMEprocedure
frame-payload-data WS-FRAMEprocedure

WebSocket messages are represented with the record type ws-frame.

For a frame f:

  • (frame-fin f) is #t iff the FIN bit is set.
  • (frame-rsv f) are the three RSV bits interpreted as an integer. For example, if exactly the RSV2 and RSV3 bits are set, (frame-rsv f) is 3.
  • (frame-opcode f) (resp. (frame-optype f) is the opcode (resp. optype) of f.
  • (frame-mask? f) is #t iff the MASK bit is set. This should be #t for every outbound frame and #f for every frame received. A ws-frame record retains no information about the masking key; masking/unmasking is handled quietly by the procedures send-frame and recv-frame.
  • The first (frame-payload-length f) bytes of the vector (frame-payload-data f) is the payload of f. Note that (frame-payload-length f) is not guaranteed to be the same as (u8vector-length (frame-payload-data f)).
frame-rsv-bit WS-FRAME Nprocedure

Is #t iff the bitwise-and of N with the result of frame-rsv is nonzero. For example, (frame-rsv-bit f 4) is #t iff the RSV1 bit is set on the frame f.

recv-frame WS-CONNECTIONprocedure
send-frame WS-CONNECTION WS-FRAMEprocedure

Receives (resp. sends) a frame from (resp. to) the server.

opcode->optype INTEGERprocedure
optype->opcode SYMBOLprocedure

Maps between opcodes and optypes:

opcodeoptype
#x0'continuation
#x1'text
#x2'binary
#x8'connection-close
#x9'ping
#xa'pong

Signals a composite condition of kind 'websocket 'exn if an unrecognised optype or opcode is supplied. The application is expected to redefine this procedure if it wishes to make use of reserved opcodes.

reason->close-code SYMBOLprocedure
close-code->reason INTEGERprocedure

Maps between reasons for closing a connection and the corresponding close codes:

code (integer)code (u8vector)reason
1000#u8(3 232)'normal-closure
1001#u8(3 233)'going-away
1002#u8(3 234)'protocol-error
1003#u8(3 235)'unsupported-data
1005#u8(3 237)'no-status-rcvd
1006#u8(3 238)'abnormal-closure
1007#u8(3 239)'invalid-frame-payload-data
1008#u8(3 240)'policy-violation
1009#u8(3 241)'message-too-big
1010#u8(3 242)'mandatory-ext
1011#u8(3 243)'internal-server-error
1015#u8(3 247)'tls-handshake

The procedure reason->close-code returns the close code as a u8vector rather than an integer; this is so that the value can be included into a frame payload without conversion.

Signals a composite condition of kind 'websocket 'exn if an unrecognised close code or reason is supplied. The application is expected to redefine this procedure if it wishes to make use of reserved close codes. Note that not all of the close codes listed here are expected to occur in a connection close frame.

The permessage-deflate extension

This library provides an implementation of the permessage-deflate extension, using zlib for compression/decompression.

permessage-deflate PARAMETERSprocedure

To offer to use this extension during the opening handshake, supply (list (permessage-deflate PARAMETERS)) as an optional argument to ws-connect.

The argument PARAMETERS should be a list in which each item is an alist, each of whose items is in turn a pairs of strings (PARAMETER . VALUE) or a pair of form (PARAMETER . #t). These specify the parameters to be advertised during the opening handshake.

For example, to ask the server to use an LZ77 sliding window of length no greater than 1024, but to fall back to permessage-deflate without this parameter if the server does not support it, one might specify

'((("server_max_window_bits" . "10")) ())

so that the Sec-WebSocket-Extensions header in the client opening handshake will contain the string

 permessage-deflate;server_max_window_bits=10,permessage-deflate

Handling exceptions

When a procedure encounters a situation which should result in the WebSocket connection being failed, it signals a composite condition of kind 'websocket 'fail, with the following properties:

reason
a symbol representing the reason for failing the connection, and
message
a string describing the exception.

The application may choose to catch and handle these exceptions, such as by calling ws-close with the corresponding arguments (see reason->close-code). The procedure recv-message-loop does this automatically.

For cases where it does not make sense to attempt to close the connection (for example if the underlying TCP connection fails, or if an error results from user input instead of a problem with data received from the server) a condition of kind 'websocket 'exn is signalled instead, with the following properties:

message
a string describing the exception.

Examples

A client which connects to localhost port 9001 without TLS, and echoes back every text message it receives from the server:

(import ws-client)

(let ((conn (ws-connect "ws://localhost:9001")))
  (recv-message-loop conn
    (lambda (m)
      (if (eq? 'text (message-type m))
          (send-text-message conn (message-data m))))))

See the examples folder in the source repository for more examples.

Author

Lo̍h Ka-tsùn

Repository

https://github.com/lohkatsun/ws-client-egg

License

BSD

Version History

1.0
0.2

Contents »