5 library(http/websocket): WebSocket support
- See also
- RFC 6455, http://tools.ietf.org/html/rfc6455
- To be done
- Deal with protocol extensions.
WebSocket is a lightweight message oriented protocol on top of TCP/IP streams. It is typically used as an upgrade of an HTTP connection to provide bi-directional communication, but can also be used in isolation over arbitrary (Prolog) streams.
The SWI-Prolog interface is based on streams and provides ws_open/3 to create a websocket stream from any Prolog stream. Typically, both an input and output stream are wrapped and then combined into a single object using stream_pair/3.
The high-level interface provides http_upgrade_to_websocket/3 to realise a websocket inside the HTTP server infrastructure and http_open_websocket/3 as a layer over http_open/3 to realise a client connection. After establishing a connection, ws_send/2 and ws_receive/2 can be used to send and receive messages. The predicate ws_close/3 is provided to perform the closing handshake and dispose of the stream objects.
- [det]http_open_websocket(+URL, -WebSocket, +Options)
- Establish a client websocket connection. This predicate calls
http_open/3 with additional
headers to negotiate a websocket connection. In addition to the options
processed by http_open, the following options are recognised:
- subprotocols(+List)
- List of subprotocols that are acceptable. The selected
protocol is available as ws_property(WebSocket,
subprotocol(Protocol)
.
The following example exchanges a message with the html5rocks.websocket.org echo service:
?- URL = 'ws://html5rocks.websocket.org/echo', http_open_websocket(URL, WS, []), ws_send(WS, text('Hello World!')), ws_receive(WS, Reply), ws_close(WS, 1000, "Goodbye"). URL = 'ws://html5rocks.websocket.org/echo', WS = <stream>(0xe4a440,0xe4a610), Reply = websocket{data:"Hello World!", opcode:text}.
WebSocket is a stream pair (see stream_pair/3) - http_upgrade_to_websocket(:Goal, +Options, +Request)
- Create a websocket connection running
call(Goal, WebSocket)
, where WebSocket is a socket-pair. Options:- guarded(+Boolean)
- If
true
(default), guard the execution of Goal and close the websocket on both normal and abnormal termination of Goal. Iffalse
, Goal itself is responsible for the created websocket. This can be used to create a single thread that manages multiple websockets using I/O multiplexing. - subprotocols(+List)
- List of acceptable subprotocols.
- timeout(+TimeOut)
- Timeout to apply to the input stream. Default is
infinite
.
Note that the Request argument is the last for cooperation with http_handler/3. A simple echo server that can be accessed at =/ws/= can be implemented as:
:- use_module(library(http/websocket)). :- use_module(library(http/thread_httpd)). :- use_module(library(http/http_dispatch)). :- http_handler(root(ws), http_upgrade_to_websocket(echo, []), [spawn([])]). echo(WebSocket) :- ws_receive(WebSocket, Message), ( Message.opcode == close -> true ; ws_send(WebSocket, Message), echo(WebSocket) ).
- throws
switching_protocols(Goal, Options)
. The recovery from this exception causes the HTTP infrastructure to callcall(Goal, WebSocket)
.- See also
- http_switch_protocol/2.
- [det]ws_send(+WebSocket, +Message)
- Send a message over a websocket. The following terms are allowed for Message:
- text(+Text)
- Send a text message. Text is serialized using write/1.
- binary(+Content)
- As
text(+Text)
, but all character codes produced by Content must be in the range [0..255]. Typically, Content will be an atom or string holding binary data. - prolog(+Term)
- Send a Prolog term as a text message. Text is serialized using write_canonical/1.
- json(+JSON)
- Send the Prolog representation of a JSON term using json_write_dict/2.
- string(+Text)
- Same as
text(+Text)
, provided for consistency. - close(+Code, +Text)
- Send a close message. Code is 1000 for normal close. See websocket documentation for other values.
- Dict
- A dict that minimally contains an
opcode
key. Other keys used are:format
:
Format- Serialization format used for Message.data. Format
is one of
string
,prolog
orjson
. See ws_receive/3. data
:
Term- If this key is present, it is serialized according to Message.format. Otherwise it is serialized using write/1, which implies that string and atoms are just sent verbatim.
Note that ws_start_message/3 does not unlock the stream. This is done by ws_send/1. This implies that multiple threads can use ws_send/2 and the messages are properly serialized.
- To be done
- Provide serialization details using options.
- [det]ws_receive(+WebSocket, -Message:dict)
- [det]ws_receive(+WebSocket, -Message:dict, +Options)
- Receive the next message from WebSocket. Message
is a dict containing the following keys:
opcode
:
OpCode- OpCode of the message. This is an atom for known opcodes and
an integer for unknown ones. If the peer closed the stream, OpCode
is bound to
close
and data to the atomend_of_file
. data
:
String- The data, represented as a string. This field is always present. String is the empty string if there is no data in the message.
rsv
:
RSV- Present if the WebSocket RSV header is not 0. RSV is an integer in the range [1..7].
If
ping
message is received and WebSocket is a stream pair, ws_receive/1 replies with apong
and waits for the next message.The predicate ws_receive/3 processes the following options:
- format(+Format)
- Defines how text messages are parsed. Format is one of
- string
- Data is returned as a Prolog string (default)
- json
- Data is parsed using json_read_dict/3, which also receives Options.
- prolog
- Data is parsed using read_term/3, which also receives Options.
- To be done
- Add a hook to allow for more data formats?
- [det]ws_close(+WebSocket:stream_pair, +Code, +Data)
- Close a WebSocket connection by sending a
close
message if this was not already sent and wait for the close reply.Code is the numerical code indicating the close status. This is 16-bit integer. The codes are defined in section 7.4.1. Defined Status Codes of RFC6455. Notably, 1000 indicates a normal closure. Data is currently interpreted as text. - Errors
websocket_error(unexpected_message, Reply)
if the other side did not send a close message in reply.
- [det]ws_open(+Stream, -WSStream, +Options)
- Turn a raw TCP/IP (or any other binary stream) into a websocket stream. Stream
can be an input stream, output stream or a stream pair. Options
includes
- mode(+Mode)
- One of
server
orclient
. Ifclient
, messages are sent as masked. - buffer_size(+Count)
- Send partial messages for each Count bytes or when flushing the output. The default is to buffer the entire message before it is sent.
- close_parent(+Boolean)
- If
true
(default), closing WSStream also closes Stream. - subprotocol(+Protocol)
- Set the subprotocol property of WsStream. This value can be retrieved
using ws_property/2. Protocol
is an atom. See also the
subprotocols
option of http_open_websocket/3 and http_upgrade_to_websocket/3.
A typical sequence to turn a pair of streams into a WebSocket is here:
..., Options = [mode(server), subprotocol(chat)], ws_open(Input, WsInput, Options), ws_open(Output, WsOutput, Options), stream_pair(WebSocket, WsInput, WsOutput).
- [nondet]ws_property(+WebSocket, ?Property)
- True if Property is a property WebSocket. Defined
properties are:
- subprotocol(Protocol)
- Protocol is the negotiated subprotocol. This is typically set as a property of the websocket by ws_open/3.