WebSocket
WebSocket is a WebSocket connection (RFC 6455), as a client or as one of a
WebSocketServer’s clients, with the same calls on macOS, iOS, Android, Linux
and Windows.
#import "WebSocket.xc" // not in the Foundation umbrella: import it by nameOverview
Section titled “Overview”try { WebSocket* ws = WebSocket.connect(Url.withString(String.withCString("ws://localhost:7070/chat"))); ws.sendText(String.withCString("hello")); WebSocketMessage* m = ws.receive(5000); // up to 5 s for a reply if (m != nil && m.isText()) Stdio.printf("%s\n", m.text().cString()); ws.close(); }catch (WebSocketError e) { Stdio.printf("%s\n", e.message().cString()); // cannot connect to localhost:7070 }A server listens on a port and completes the handshake with each client that
connects; what it gets back is a WebSocket like any other, with the request’s
path and headers:
WebSocketServer* server = WebSocketServer.listen(7070);for (;;) { WebSocket* c = server.accept(-1); // a client, handshake done if (c == nil) continue; Stdio.printf("%s\n", c.path().cString()); // /chat?room=1 WebSocketMessage* m; while ((m = c.receive(-1)) != nil) c.sendText(m.text()); // echo }Messages are text (UTF-8) or binary, whole: a fragmented message is
reassembled before receive returns it. A ping from the peer is answered with
a pong inside receive, and a pong to one of yours is consumed there. A close
from the peer is answered and reported: receive returns nil, isOpen is
false and closeCode holds its code. A client masks what it sends, as the
protocol requires; a server does not.
Reading blocks only as long as you say. receive waits up to its ms for
a message to start, so a program can poll from a timer and the
run loop keeps turning; -1 waits for as long as it
takes. A connection the peer closes, or that fails, reads as closed from then
on.
wss: goes through the TLS layer Http uses: import
HttpTls.xc and call HttpTls.install() first, and connect verifies the
server’s certificate as https does. A WebSocketServer takes plain ws:
connections; put a TLS-terminating proxy in front of it for wss:.
In a browser (wasm32)
Section titled “In a browser (wasm32)”On wasm32 the client is the host’s own WebSocket: the browser’s, or Node’s. A page’s thread cannot block, so what waits is different:
connectreturns at once with the connection still opening. What you send before it opens is queued and goes when it does. A connection that fails is not a throw: the delivery’s last call isnilandcloseCodeis 1006.- Messages arrive through
deliver, called from the host’s event loop aftermainhas returned.receivehands back only a message that has already arrived. - There is no
WebSocketServer: a page cannot listen.
WebSocket* ws = WebSocket.connect(Url.withString(String.withCString("wss://example.com/feed")));ws.sendText(String.withCString("hello"));ws.deliver(block void(WebSocketMessage* m) { if (m == nil) { Stdio.printf("closed %u\n", (u32)ws.closeCode()); return; } Stdio.printf("%s\n", m.text().cString()); });Topics
Section titled “Topics”Connecting · connect · isOpen · close · closeWith
Sending · sendText · sendData · sendBytes · ping
Receiving · receive · deliverOn · WebSocketMessage
After a close · closeCode · closeReason
Serving · WebSocketServer
Errors · WebSocketError
Connecting
Section titled “Connecting”connect
Section titled “connect”static WebSocket* connect(Url* url) throwsOpens url (ws: or wss:; the port defaults to 80 or 443) and completes
the handshake, blocking until it is done or refused. Throws a
WebSocketError saying why when it cannot be: the host, a
refused connection, a TLS failure, or a reply that is not 101 with the right
Sec-WebSocket-Accept.
isOpen
Section titled “isOpen”bool isOpen(void)void close(void)The closing handshake with code 1000: sends a close frame, waits up to two seconds for the peer’s, and closes the connection. A connection is also closed when it is freed, without the handshake.
closeWith
Section titled “closeWith”void closeWith(u16 code, String* reason)The same with a code of your own and a reason (may be nil).
Sending
Section titled “Sending”sendText
Section titled “sendText”bool sendText(String* s)A text message, the String’s UTF-8 bytes. False when the connection has gone (it is then closed).
sendData
Section titled “sendData”bool sendData(Data* d)A binary message.
sendBytes
Section titled “sendBytes”bool sendBytes(u8* p, u32 n)A binary message of n bytes at p.
bool ping(void)A ping; the peer’s pong is consumed by receive.
Receiving
Section titled “Receiving”receive
Section titled “receive”WebSocketMessage* receive(i32 ms)The next message, waiting up to ms milliseconds (0: not at all, -1: for as
long as it takes) for one to start; nil when none starts in time, and nil
once the connection is closed — isOpen tells which. Pings are
answered and pongs consumed on the way. A frame over 64 MB, or a frame that
breaks the protocol, closes the connection with code 1009 or 1002.
deliverOn
Section titled “deliverOn”void deliverOn(RunLoop* loop, block onMessage void(WebSocketMessage*))void deliver(block onMessage void(WebSocketMessage*))Messages as callbacks. receive runs on a thread of its own, and each
message is handed to onMessage on the thread running loop, in order. The
last call has nil: the connection has closed. Do not call receive on the
socket once it is delivering. deliver does the same without a run loop: the
calls come on the receive thread itself, or on wasm32 from the host’s event
loop (the only form there).
RunLoop* loop = RunLoop.main();ws.deliverOn(loop, block void(WebSocketMessage* m) { if (m == nil) { loop.stop(); return; } Stdio.printf("%s\n", m.text().cString()); });loop.run();WebSocketMessage
Section titled “WebSocketMessage”bool isText(void)String* text(void) // the bytes as a StringData* data(void) // the bytesAfter a close
Section titled “After a close”closeCode
Section titled “closeCode”u16 closeCode(void)The peer’s code when it closed (1005 when it sent none), 1006 when the connection went without a close frame, or the code this side closed with.
closeReason
Section titled “closeReason”String* closeReason(void)The peer’s reason, or nil.
The handshake
Section titled “The handshake”String* path(void)On a server’s connection, the request’s path with its query (/chat?room=1);
on a client’s, the path it asked for.
header
Section titled “header”String* header(String* name)A header of the handshake — the request’s on a server’s connection, the
reply’s on a client’s — by its name in any case; nil when absent.
WebSocketServer
Section titled “WebSocketServer”static WebSocketServer* listen(u16 port) throwsWebSocket* accept(i32 ms)void close(void)listen binds the port (throws a SocketError when it cannot). accept
waits up to ms milliseconds (-1: for as long as it takes) for a client, reads
its handshake and answers it; nil when none connects in time, or when what
connected was not a WebSocket handshake (it is answered 400 and closed).
WebSocketError
Section titled “WebSocketError”class WebSocketError <Error>String* message(void)What connect throws.