Use WebSockets with q¶
This page explains how to implement WebSocket servers and clients in q for real-time, bidirectional communication, and how to publish live data to a browser.
q has supported the WebSocket protocol since v3.0. A WebSocket is a persistent, bidirectional, full-duplex channel over a single TCP connection, so the server can push data to the client the moment it becomes available. That makes WebSockets a good fit for interactive applications such as trading dashboards, live feeds, and chat systems.
For more on WebSockets in q, see the I/O chapter in Q for Mortals.
Create a WebSocket server¶
To turn a q process into a WebSocket server, listen on a port and define the .z.ws message handler. The server runs that callback for every message a client sends.
An echo server¶
This server returns every message it receives to the sender.
-
Start a q session listening on port 5000:
q)\p 5000 -
Define the
.z.wscallback. Sending on the negative handleneg[.z.w]returns the incoming messagexto the client on the current connection handle,.z.w:q).z.ws: {neg[.z.w] x}
WebSocket messages are always asynchronous, which is why the callback sends on a negative handle.
Test the server with any WebSocket client. For example, open the ws.htm sample client in a browser and connect to ws://localhost:5000. Whatever you send comes back in the output area.
Free the port between examples
Every example on this page uses port 5000. Before you start another one, close the previous q process or pick a different port, or the new process can't bind.
A remote evaluation server¶
Redefine .z.ws to evaluate each message as q code, and .Q.s to format the result for display:
q).z.ws: {neg[.z.w] .Q.s value x}
This server runs arbitrary code from any client
Never expose raw value to untrusted clients. A client that can connect can run anything the q process can.
Use a command pattern instead: parse the input, check the command against an allowlist, and dispatch to a specific handler. See authentication and authorization.
A trap (@) returns an error in the submitted code to the client instead of leaving the callback to fail:
/ if 'value x' fails, the handler formats the error message for the client
q).z.ws: {neg[.z.w] @[.Q.s value@; x; {"'", x, "\n"}]}
Handle WebSocket events¶
q manages the WebSocket lifecycle through callbacks in the .z namespace:
| Callback | Runs when |
|---|---|
.z.ws |
A message arrives from a client |
.z.wo |
A client opens a connection. Receives the connection handle |
.z.wc |
A client closes the connection. Receives the connection handle |
Within any of these, .z.w holds the integer connection handle of the client being served.
Define .z.wo and .z.wc to track the connections a server holds:
q)activeWSConnections: ([handle: ()] connectTime: ()) / keyed by handle
q).z.wo: {`activeWSConnections upsert (x; .z.t)} / x is the new handle
q).z.wc: {delete from `activeWSConnections where handle = x}
Inspect every socket, not just the WebSockets
-38! reports all current socket connections, WebSockets included.
Send data and manage connections¶
Send to a WebSocket client on the negative connection handle, neg[h], which is always asynchronous:
q)neg[h] "Hello, client!" / one client
q)handles: exec handle from activeWSConnections / or every client
q)neg[handles]@\:"Market is now open"
Close connections¶
Close a connection with hclose. Flush it first, so that any queued outgoing message is sent before the handle goes away:
q)neg[h][] / flush pending data, blocking until it is sent
q)hclose h / close the handle
To close every active WebSocket connection:
q){neg[x][]; hclose x} each exec handle from activeWSConnections
Create a WebSocket client¶
A q process can act as a WebSocket client and connect to other servers. Available since v3.2t.
Define .z.ws before you connect
Define .z.ws first. If you don't, connecting signals a '.z.ws undefined error.
.z.ws serves both roles
The same callback handles incoming messages whether the process is acting as a server or as a client. As a client, .z.ws receives whatever the remote server sends.
Open a connection by sending an HTTP Upgrade request to the target server:
q).z.ws: {0N!x} / handle incoming messages first
/ `$":ws://host:port" builds a connection handle for the request
q)h: (`$":ws://127.0.0.1:5000")"GET / HTTP/1.1\r\nHost: 127.0.0.1:5000\r\n\r\n"
On success the result is a two-item list: the integer connection handle, and the server's HTTP 101 ("Switching Protocols") response.
q)h
(6i;"HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=\r\n\r\n")
If the server refuses the upgrade, the handle is a null integer, 0Ni:
q)h
(0Ni;"HTTP/1.1 400 Bad Request\r\n...")
Other problems, such as a host that does not exist, signal an ordinary q error.
A q client and a q server¶
-
Server. Start a q process on port 5000, and define
.z.wsto reply with a formatted string:q)\p 5000 q).z.ws: {neg[.z.w] "server replied with ", $[10 = type x; x; raze string x];} -
Client. Define
.z.wsto print what arrives, then connect:q).z.ws: {0N!"Client received msg:"; 0N!x;} q)r: (`$":ws://127.0.0.1:5000")"GET / HTTP/1.1\r\nHost: 127.0.0.1:5000\r\n\r\n" -
Client. Send messages on the handle from the connection response,
r 0:q)neg[r 0]"test" / a string q)neg[r 0]0x010203 / a byte vectorThe client console shows the server's replies:
"Client received msg:" "server replied with test" "Client received msg:" "server replied with 010203" -
Client. Close the connection, again using the first item of the connection result:
q)hclose r 0
Authentication and security¶
On the server¶
A WebSocket connection starts as an HTTP Upgrade request, so secure the handshake with the .z.ac HTTP authentication callback. It integrates with standard mechanisms such as LDAP, OAuth 2.0, and OpenID Connect.
On the client¶
To authenticate a client, add credentials to the HTTP Upgrade request header, such as a bearer token or a session cookie.
For basic access authentication, put the username and password in the connection string:
q)`:ws://username:password@127.0.0.1:5001 "GET / HTTP/1.1\r\nHost: 127.0.0.1:5001\r\n\r\n"
Secure connections (WSS)¶
For an encrypted WebSocket connection (WSS), first configure the q process for SSL/TLS, then use the wss:// scheme in place of ws://.
Or handle TLS outside q
A tool such as stunnel can wrap a plain WebSocket server, which keeps TLS termination out of the q process altogether.
Serialize data¶
JSON¶
JSON is the standard format for web communication, and q supports it natively. .j.k parses an incoming JSON string into a q dictionary, and .j.j serializes q data into a JSON string.
/ parse JSON, read "cmd", and reply with a JSON confirmation
.z.ws: {
d: .j.k x; / JSON string to dictionary
res: `cmd`status!(d`cmd; "received"); / the response dictionary
neg[.z.w] .j.j res / serialize to JSON and send
}
The q IPC format (c.js)¶
For better performance with q-specific data types, send binary serialized data instead. The client then has to deserialize that format — in a browser, with the c.js library.
In q, -8! encodes data to bytes and -9! decodes bytes back to a q object.
The wslogin.htm example sends a JavaScript dictionary to q using c.js:
-
Start the q server on port 5000. Its
.z.wsdecodes the incoming byte vector, takes the values from the resulting dictionary, and sends them back serialized:q)\p 5000 q).z.ws: {neg[.z.w] -8!value -9!x;} -
Open
wslogin.htmin a browser, withc.jsin the same directory. -
Click login. The browser serializes the JavaScript dictionary
{u:"user", p:"At0mbang."}and sends it. -
The server deserializes it into the q dictionary
`u`p!("user";"At0mbang.")and returns the serialized values,("user";"At0mbang."), to the browser.
Performance and encoding¶
Compression¶
q supports permessage-deflate WebSocket compression, which reduces network traffic. Client and server negotiate it during the handshake. In Chrome DevTools, a successful negotiation shows the Sec-WebSocket-Extensions: permessage-deflate header in the HTTP response.
To measure the effect, use \ts. Compressed data takes longer to queue, for more CPU, and uses less bandwidth:
q)v: 10000#.Q.a / compressible data
/ queue 1000 messages uncompressed: quicker to queue, more memory and bandwidth
q)\ts do[1000; (-5)v]; show sum each .z.W
5| 10004000
6| 0
14 20610976
/ queue 1000 messages compressed: slower to queue, far smaller payload
q)\ts do[1000; (-6)v]; show sum each .z.W
5| 0
6| 47022
94 4354944
Turn compression off per connection
To disable compression for one client connection, for example to favor lower CPU use over bandwidth, send the sec-websocket-protocol HTTP header with the value kxnodeflate:
// a JavaScript client disabling compression
let ws = new WebSocket(url, "kxnodeflate");
UTF-8 encoding¶
Text messages must be UTF-8
The WebSocket protocol requires every text message to be UTF-8 encoded. Sending text in another encoding signals a 'utf8 error.
A real-time data publisher¶
This example builds a publish-and-subscribe service: a q process pushes live trade and quote updates to a web browser.
Set up¶
- Download the project files from kxcontrib/websocket.
- Run
q pubsub.qto start the publisher. - Run
q fh.qto start a feed handler that generates sample data. - Open
websockets.htmlin a browser to watch the updates arrive.
The server: subscription management¶
The web client subscribes to server-side functions by name. The server's .z.ws evaluates each incoming message, which lets the client call functions such as sub:
/ pubsub.q: let the client call functions directly
.z.ws: {value x}
As the security warning above explains, evaluating client input like this is safe only on a trusted network. In production, restrict the client to an allowlist of functions.
A subs table records the active subscriptions: the client handle, the function subscribed to, and its arguments.
q)subs: 2!flip `handle`func`params!"is*"$\:()
When a client connects, it calls sub, which records its subscription:
/ x: the function to subscribe to, y: the parameters for it
sub: {`subs upsert (.z.w; x; enlist y)}
The server: publishing¶
A .z.ts timer calls pub for each subscription. pub runs the subscribed function and pushes the result to that client, serialized to JSON by .j.j:
/ pubsub.q
pub: {
row: (0!subs) x;
/ run the subscribed function, such as getTrades`AAPL`GOOG
newData: (value row`func) row`params;
/ send the update to the client as a JSON string
(neg row`handle) .j.j `func`result!(row`func; newData);
}
Set the timer to publish every subscription once a second:
.z.ts: {pub each til count subs}
\t 1000
The client: handling updates¶
In the browser, the JavaScript onmessage handler parses the incoming JSON. The server includes an identifier so the client can route each update to the right part of the page — here, the name of the subscribed function:
// websockets.html
ws.onmessage = function(e) {
// parse the JSON string from the server
var d = JSON.parse(e.data);
// route on 'func' to the matching UI update handler
switch(d.func){
case 'getSyms' : setSyms(d.result); break;
case 'getQuotes' : setQuotes(d.result); break;
case 'getTrades' : setTrades(d.result); break;
}
};
Next steps¶
- IPC basics — the q IPC protocol that WebSockets sit alongside.
- Asynchronous communication — negative handles, flushing, and broadcasts, which WebSockets rely on throughout.
- Secure and monitor IPC connections — restrict what a connected client can run.
- Encrypt connections with SSL/TLS — the TLS configuration behind
wss://. - REST server module — serve HTTP and HTTPS requests from q.