Did you know ... | Search Documentation: |
![]() | websocket.pl -- WebSocket support |
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.
subprotocol(Protocol)
.
Note that clients often provide an Origin header and some servers
require this field. See RFC 6455 for details. By default this
predicate does not set Origin. It may be set using the
request_header
option of http_open/3, e.g. by passing this in the
Options list:
request_header('Origin' = 'https://www.swi-prolog.org')
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}.
call(Goal, WebSocket)
,
where WebSocket is a socket-pair. Options:
true
(default), guard the execution of Goal and close
the websocket on both normal and abnormal termination of Goal.
If false
, Goal itself is responsible for the created
websocket if Goal succeeds. The websocket is closed if Goal
fails or raises an exception. This can be used to create a single
thread that manages multiple websockets using I/O multiplexing.
See library(http/hub).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) ).
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.text(+Text)
, provided for consistency.opcode
key. Other keys
used are:
string
, prolog
or json
. See ws_receive/3.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.
close
and data to the atom
end_of_file
.
If ping
message is received and WebSocket is a stream pair,
ws_receive/1 replies with a pong
and waits for the next
message.
The predicate ws_receive/3 processes the following options:
close
message if
this was not already sent and wait for the close reply.
server
or client
. If client
, messages are sent
as masked.true
(default), closing WSStream also closes Stream.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).
text
,
binary
, close
, ping
or pong
. RSV is reserved for
extensions. After this call, the application usually writes data
to WSStream and uses ws_send/1 to complete the message.
Depending on OpCode, the stream is switched to binary (for
OpCode is binary
) or text using utf8
encoding (all other
OpCode values). For example, to a JSON message can be send
using:
ws_send_json(WSStream, JSON) :- ws_start_message(WSStream, text), json_write(WSStream, JSON), ws_send(WSStream).
close
, close the stream.false
, it will wait for an
additional message.ping
is received, it will reply with a pong
on the
matching output stream.pong
is received, it will be ignored.close
is received and a partial message is read,
it generates an exception (TBD: which?). If no partial
message is received, it unified OpCode with close
and
replies with a close
message.If not all data has been read for the previous message, it will first read the remainder of the message. This input is silently discarded. This allows for trailing white space after proper text messages such as JSON, Prolog or XML terms. For example, to read a JSON message, use:
ws_read_json(WSStream, JSON) :- ws_read_header(WSStream, OpCode, RSV), ( OpCode == text, RSV == 0 -> json_read(WSStream, JSON) ; OpCode == close -> JSON = end_of_file ).