WebSocketClient
| Repository | https://github.com/dyalog/websocketclient |
| Copyright | Made with Material for MkDocs. Contents copyright ©2015-2026 Dyalog, LTD |
Overview
WebSocketClient is an APL-based, cross-platform utility that can be used to communicate with WebSocket servers.
What is a WebSocket?
A WebSocket is a continuous, two-way connection between a client and an HTTP server that stays open rather than closing after every interaction like a standard web request. By completing a one-time "handshake" to open this persistent channel, both the client and the server can instantly send and receive data at the exact same time without the lag and overhead of constantly polling the server for updates. WebSockets are useful for any application that requires real-time interaction, such as live chat applications, multiplayer games, collaborative documents, and live stock tickers.
Terminology
WebSocketClient is a class written in Dyalog APL that implements a WebSocket client. Since it's both the name of the class and the name of the underlying technology, we'll use the following conventions:
- "
WebSocketClient" (in the APL font) refers to the class. - "WebSocket" (in a non-APL font) refers to the technology.
- "
ws" is the name we'll use in this documentation to refer to an instance ofWebSocketClient. Obviously, you can name it whatever you like in your application.
Obtaining WebSocketClient
You can obtain WebSocketClient in any of the following ways:
- Clone or download the zip file from the
WebSocketClientrepository - Download the
WebSocketClient.aplcfile from the latest release ofWebSocketClient. - Use Tatin to load
WebSocketClient-]TATIN.LoadPackages WebSocketClient
Note: You will need to activate Tatin before you can use the Tatin user commands.
Your First WebSocketClient
WebSocket.org has a public server that you can use to test WebSocket connections in real time. After having obtained WebSocketClient you can do the following:
ws←WebSocketClient.New 'wss://echo.websocket.org'
ws.Connect
0 Connected
>>> Request served by 4d896d95b55478
First we created a new instance WebSocketClient with the server's URL. echo.websocket.org is a publicly available server for testing WebSockets. Next we created the WebSocket connection using the Connect function. Running Connect will start a "listener" thread to receive any incoming messages. By default WebSocketClient will display the messages it receives to the session prefixed by >>>. Once connected, echo.websocket.org will echo back whatever it receives.
ws.Send 'hello world'
>>> hello world
Finally, we can close the WebSocket using the Close function.
ws.Close
0 Closed
Further Reading
- For general information about WebSockets see WebSocket.org.
- For information about Conga's WebSocket support see the Conga User Guide
Usage Guide
The Basic Steps
- Write a function that will be called whenever your WebSocket receives a message - you'll assign the function name to the
OnWSReceivesetting of your client. - Create an instance of
WebSocketClient. - Configure the instance.
- Run
ws.Connectto connect to the WebSocket server and start listening for messages. - Close the WebSocket
Each of these steps is described in more detail below.
1. Write a function for OnWSReceive.
A WebSocket exists to deliver messages to you, so the first thing to decide is what your application should do with a message when it arrives. WebSocketClient's default behavior is to display received messages in the session prefixed by >>> which can be useful when experimenting, but not much use in a real application. Instead you write a "hook" function and assign its name to the OnWSReceive setting. The hook is called on the listener thread for each message segment that arrives, with the client instance as its left argument and MsgState - the namespace in which WebSocketClient assembles the incoming message - as its right argument.
2. Create an instance of WebSocketClient.
Use the shared New method (ws←WebSocketClient.New args) rather than ⎕NEW, because New traps the errors that ⎕NEW would signal. args may be:
''- an instance with all settings at their default values.- a namespace whose variables are the settings to apply, for example
(URL:'echo.websocket.org' ⋄ OnWSReceive:'OnMessage'). - a vector of settings in the positional order
URLOnWSReceiveOnWSUpgradeProtocolHeadersParams
for example'echo.websocket.org' 'OnMessage'.
If construction fails, New returns a namespace reporting the failure instead of an instance, so check its rc or Connected before going on. Setting Debug to 1 has the error signalled instead.
3. Configure the instance.
Any setting not supplied to New can be assigned directly to the instance at any time before Connect is called. For instance:
ws.(URL OnWSReceive)←'wss://echo.websocket.org' 'OnMessage'
The ws.Config method returns a 2-column matrix of every public field and its current value, which is a convenient way to check what you've set.
4. Run ws.Connect.
Connect initializes Conga if that hasn't happened yet, builds and sends the WebSocket upgrade request from your settings, follows any redirections, and completes the handshake. It returns (rc msg) - 0 'Connected' on success - and also leaves the result in the instance's rc and msg fields. On success it starts the listener thread, which sits in a Conga wait loop calling your OnWSReceive hook whenever a message is received until the WebSocket is closed. From then on you can Send messages, and when you're done, Close shuts the WebSocket down and stops the listener.
5. Close the WebSocket
A WebSocket connection ends in one of two ways - your application closes it, or the
other end does. Either way the listener thread stops and
Connected drops back to 0, which is the reliable
test of whether the WebSocket is still usable. Nothing else announces itself: a
connection that has gone away does not interrupt your code, and Send simply fails
rather than signalling an error.
ws.Send 'anyone there?'
¯1 No client connection has been established
Closing from your side
Close signals the listener thread to stop, closes the
Conga connection, and clears Connection:
ws.Close
0 Closed
Like Connect, Close returns (rc msg) and leaves the same pair in the instance's
rc and msg fields. It is safe
to call more than once - a second call returns 0 'Already closed' - and an instance
that was never connected returns 0 'Not listening'.
Close may take up to WaitTime milliseconds (5
seconds by default) to return, because that is how long the listener can be sitting
in a Conga Wait before it notices that it has been asked to stop. Close polls for
WaitTime×1.1 milliseconds and then terminates the thread with ⎕TKILL, so it
always returns; if a prompt shutdown matters more to your application than an idle
listener waking rarely, lower WaitTime.
Because Close stops the listener directly rather than by way of a Conga Closed
event, your OnClose hook is not called - a
close you asked for is not news to your application.
Always close a connection you are finished with. It is tempting to assume that expunging the instance is enough - the class has a destructor that closes the connection and kills the listener - but the destructor does not run while the listener thread is still going, and the listener is what keeps the instance alive. Expunging the name therefore leaves you with an orphaned listener: a thread still waiting on a live connection, belonging to an instance you no longer have a reference to.
The reference is recoverable. ⎕INSTANCES returns every live instance of the class,
so you can find the orphan and close it properly:
ws.Connected
1
⎕EX 'ws'
1
⎕TNUMS ⍝ the listener is still running
0 1
inst←⎕INSTANCES #.WebSocketClient
≢inst
1
(⊃inst).URL
wss://echo.websocket.org
(⊃inst).Close
0 Closed
⎕TNUMS
0
Once Close has stopped the listener, nothing is holding the instance any longer and
it is discarded - the destructor runs, and ⎕INSTANCES comes back empty.
If several instances are live, ⎕INSTANCES gives you all of them, so read URL or
Config to tell them apart - or simply close the lot:
{}(⎕INSTANCES #.WebSocketClient).Close
Closed by the server
A close can equally come from the other end - the server shutting down, an idle
timeout, or a proxy dropping the connection. The listener sees Conga's Closed
event, calls your OnClose hook if you have set one, and then terminates, setting
Connected to 0. The event itself is left in
LastWaitResponse for inspection afterwards.
An application that needs to react to this - to reconnect, to warn the user, to stop
queueing work that can no longer be sent - either checks Connected before it relies
on the connection, or sets an OnClose hook:
∇ (rc msg)←client OnClosed waitData
[1] ⍝ waitData is Conga's Wait result: (return code) (object name) 'Close' (data)
[2] ⎕←'WebSocket to ',client.URL,' was closed by the server'
[3] Reconnect←1 ⍝ ... and let the rest of the application know
[4] (rc msg)←0 'Closed by server'
∇
ws.OnClose←'OnClosed'
The hook is called on the listener thread, as OnWSReceive is, so the cautions in
Two things to be careful about apply to it too:
keep it short, and trap anything it might signal. OnError is its
counterpart for a connection that Conga reports an error on - it is called in the
same way, and the listener ends after it in the same way.
After the server has closed the connection there is nothing left for Close to do,
and it reports 0 'Already closed'.
Connecting again
A closed instance is not a spent one. Calling Connect
again negotiates a fresh connection using the same settings, so an instance can be
opened and closed as often as your application needs:
ws.Close
0 Closed
ws.Connect
0 Connected
Connect clears the status fields before it begins, so anything you want to know
about the connection that has just ended - ErrorInfo
after a listener that stopped on an error, LastWaitResponse after a close - has to
be read before you reconnect.
More about OnWSReceive
The functionality of OnWSReceive and the format of the message payload is entirely up to the specifications of your application. Let's say we've connected to a ficticious online chat service where the payload format is JSON and contains the message sender's id and the message they sent - something like {"id":"Daffy","msg":"Quack!"} and you want to display the sender and message to your APL session.
∇ client OnMessage state;json
[1] ⍝ state is WebSocketClient's MsgState namespace containing buffer, payload, final, opcode
[2] ⍝ client is a reference to the instance in case you need its fields or methods
[3] ⍝ Since WebSocketClient reassembles fragmented messages for us, there is
[4] ⍝ nothing to do until the message is complete
[5] :If state.final
[6] :Trap 11 ⍝ in case the message isn't JSON
[7] json←⎕JSON state.buffer
[8] ⎕←json.id,' says "',json.msg,'"'
[9] :Else
[10] ⎕←'*** JSON import failed on: "',state.buffer,'"'
[11] :EndTrap
[12] :EndIf
∇
Since echo.websocket.org simply echoes back what it receives, we can test our hook function...
ws←WebSocketClient.New (URL:'echo.websocket.org' ⋄ OnWSReceive:'OnMessage')
ws.Connect
0 Connected
*** JSON import failed on: "Request served by 4d896d95b55478"
The first response from echo.websocket.org is informational and not JSON. But now we can send a JSON payload to echo.websocket.org and it will echo it back...
ws.Send ⎕JSON (id:'Daffy' ⋄ msg:'Quack!')
0
Daffy says "Quack!"
ws.Close
0 Closed
Partial Messages
The WebSocket protocol allows for messages to be sent one or more fragments. See Partial Messages for more information.
Two things to be careful about
Trap your own errors. The listener runs the whole of its wait loop inside a single
error trap, and your OnWSReceive hook runs inside that loop. An error in the hook
does not suspend the listener thread, but it does end it: the listener records ⎕DMX
in the instance's ErrorInfo field, closes the Conga connection, and sets Connected
to 0. The same applies to an error anywhere else on that thread - in OnClose or
OnError, in the UTF-8 translation of an incoming message, or in Conga itself.
So a hook that fails on one awkward message takes the WebSocket down with it, and if you want to survive that message you have to trap it yourself:
∇ client OnMessage state
[1] :If state.final
[2] :Trap 0
[3] Handle state.buffer
[4] :Else
[5] ⎕←'bad message ignored: ',⊃⎕DMX.DM
[6] :EndTrap
[7] :EndIf
∇
What the trap in the listener buys you is that a failure is reported rather than silent. A stopped listener says why:
ws.Connected
0
ws.ErrorInfo.EM
DOMAIN ERROR
ws.ErrorInfo.DM
DOMAIN ERROR OnMessage[3] json←⎕JSON state.buffer ∧
ErrorInfo is '' until something is caught, and ws.ListenerThread∊⎕TNUMS tells
you whether the listener is still running. Between them they distinguish the three
ways a listener can be gone: closed normally by the server, ended by an error with
ErrorInfo set, or - with Debug non-zero, which turns
the trap off - suspended in the debugger, which is how you want to develop a hook in
the first place.
MsgState is left exactly as the failing hook saw it, since the reset that normally
follows a final segment is skipped, so it is worth inspecting alongside ErrorInfo
when working out what the hook choked on.
Do not keep the MsgState reference. It is one namespace reused for every
message, and WebSocketClient clears it as soon as your hook returns for a final
segment. A hook that queues the reference for another thread, or stores it for later,
will find it empty or holding some later message. Copy out what you need:
[3] Queue,←⊂state.(buffer opcode) ⍝ the values, not the namespace
More about Send
Send puts a message on the WebSocket. What the server
makes of it is between you and the server, but two things are decided by
WebSocketClient: whether the message goes out as text or as binary, and what you get
told about it.
Text or Binary
The WebSocket protocol has two kinds of message, and the datatype of the argument
chooses between them - character data is sent as a text message (opcode 1), integer
data as a binary one (opcode 2):
ws.Send 'hello ⍺⍵' ⍝ text
0
ws.Send 72 73 74 ⍝ binary
0
There is nothing to set and nothing to encode. Conga translates character data to UTF-8 on the way out and back from UTF-8 on the way in, so a message with APL glyphs in it needs no special handling at either end.
Binary data must be integers that fit in a byte. Anything else is refused by Conga
rather than by WebSocketClient, which is worth recognising when you see it:
ws.Send 1.5 2.5
1004 Conga send failure: 1004
An empty message is legal, and is sent as an empty text message.
Check the Result
Send returns (rc msg), 0 '' when the data went out:
ws.Send 'hello'
0
Unlike Connect and Close,
Send does not leave its result in the instance's rc and msg fields - those
still describe the last connect or close - so the result has to be taken from Send
itself:
⎕←(rc msg)←ws.Send payload
The three failures worth handling separately are:
| Result | Meaning |
|---|---|
¯1 'No client connection has been established' |
There is no connection - it was closed, or never made |
1004 'Conga send failure: 1004' (any Conga rc) |
Conga refused or could not send the data |
¯1 '... occurred trying to send' |
An APL error was trapped while sending |
A 0 from Send means the data reached Conga, not that the server processed it or
agreed with it - a WebSocket send has no reply. If your protocol has one, it arrives
later, on the listener thread, through your OnWSReceive hook.
Sending a Message in Pieces
Send takes an optional second element saying whether the data completes the message,
which lets a large or open-ended message be sent as a sequence of fragments:
ws.Send ('part one ' 0) ⍝ 0 - more to come
0
ws.Send ('part two' 1) ⍝ 1 - that was the last of it
0
The receiving end sees one message, 'part one part two'. WebSocketClient sets the
continuation opcodes itself, which is why every fragment of one message has to be of
the same datatype:
ws.Send ('abc' 0)
0
ws.Send (1 2 3) 1
¯1 Datatype is not the same as previous fragment (1)
Note that the message is still open after that failure - the fragment was rejected, not the message. Sending Partial Messages covers this, and how to abandon a message you have started, in more detail.
Troubleshooting
This section describes in more depth the details of how WebSocketClient works.
WebSocket Handshake
A WebSocket connection begins life as an ordinary HTTP request. The client asks the
server to change protocols, and a server willing to do so answers 101 Switching
Protocols; from that point the socket carries WebSocket frames rather than HTTP.
Connect performs this exchange for you, but it is worth knowing what it sends, what
it does with the answer, and where the answer is left for you to look at.
What Is Sent
Conga composes the upgrade request, and WebSocketClient supplies three things to put
in it: the path (the path from URL, plus a query string
built from the URL's own and from Params), the host,
and the headers.
The headers are assembled in this order:
Headers, as you have built it up withAddHeader,SetHeader, and friends.Sec-WebSocket-ExtensionsfromExtensionsandSec-WebSocket-ProtocolfromProtocol- both added only if you have not set that header yourself.Authorization, fromAuthandAuthTypeif they are set (overwriting anyAuthorizationheader you set directly), or from credentials embedded in the URL if they are not.HeaderSubstitutionis applied to the result, replacing delimited environment-variable references with their values.- Headers with empty values are dropped.
Conga adds the protocol's own mandatory headers - Upgrade, Connection,
Sec-WebSocket-Key, and Sec-WebSocket-Version - so you neither need to nor should
set those yourself.
What Comes Back
Connect then waits WaitTime milliseconds for a
single Conga event, and what arrives decides the outcome:
| Event | Meaning |
|---|---|
WSUpgrade |
The server upgraded, and Conga has already validated the response (AutoUpgrade is 1) |
WSResponse |
The server responded and it is yours to validate (AutoUpgrade is 0) |
HTTPHeader |
An ordinary HTTP response - a redirection, or a refusal |
Timeout |
Nothing arrived within WaitTime; Connect returns 100 'Conga connection timed out' |
Error |
Conga reported an error, which becomes the rc |
Closed |
The server closed the socket instead of answering; Connect returns 'Socket closed by server' |
On either of the first two, the response is parsed into
WSUpgradeResponse before your hook sees
it, and it stays there after Connect returns:
ws.Connect
0 Connected
ws.WSUpgradeResponse.(version status message)
HTTP/1.1 101 Switching Protocols
ws.WSUpgradeResponse.headers
upgrade websocket
connection Upgrade
sec-websocket-accept vKfjfb62aH5uFI4KM/un/ixCh3k=
date Sat, 05 Sep 2026 19:01:09 GMT
server Fly/ec1a4f957c (2026-08-31)
ws.WSUpgradeResponse.headers ws.GetHeader 'upgrade'
websocket
status is a number, headers is a 2-column matrix that
GetHeader will search for you when passed as its left
argument, and payload holds anything that followed the headers - normally empty.
WSUpgradeResponse is '' if the handshake never got as far as a response.
Vetting the Handshake Yourself
Even with AutoUpgrade left at 1, an OnWSUpgrade
hook gets to see the parsed response and can veto the connection by returning a
non-zero rc, which Connect returns as its own result. This is where to check that
the server agreed to what you asked for - a sub-protocol, most usefully, since a
server is free to ignore the request and speak its own dialect instead:
∇ (rc msg)←client OnUpgrade response
[1] ⍝ refuse the connection unless the server agreed to our sub-protocol
[2] (rc msg)←0 ''
[3] :If 'chat'≢response.headers client.GetHeader 'sec-websocket-protocol'
[4] (rc msg)←¯1 'server did not accept the chat sub-protocol'
[5] :EndIf
∇
ws.(Protocol OnWSUpgrade)←'chat' 'OnUpgrade'
Setting AutoUpgrade to 0 goes further: Conga hands over the response without
validating it, WebSocketClient calls your
OnWSResponse hook, and only if that returns
0 does it accept the upgrade. The hook is then responsible for whatever checking the
WSUpgrade path would have done for you, so leave AutoUpgrade at 1 unless you
have a specific reason not to.
Both hooks run on the thread that called Connect, and errors in them are trapped -
Connect returns ¯1 and a msg beginning 'Unexpected ' rather than suspending,
unless Debug is non-zero.
Redirections
A server that answers with 301, 302, 303, 307, or 308 arrives as an
HTTPHeader event, and Connect starts again against the Location header - up to
MaxRedirections times. Each hop is recorded
in Redirections as a namespace holding the URL
that was tried and the response it produced, so a connection that ended up somewhere
unexpected can be traced afterwards. A redirection without a Location header, or one
too many hops, ends the attempt.
Any other HTTP status is a refusal: Connect returns
¯1 'Unexpected server response: ...' with the status and message, and
HttpStatus,
HttpMessage, and
HttpHeaders hold the response for inspection.
When the Handshake Fails Quietly
Not every server that declines to upgrade says so in HTTP. Asking
echo.websocket.org for a sub-protocol it does not support, for example, gets no
response at all - the server simply closes the socket:
ws.Protocol←'chat'
ws.Connect
1119 Socket closed by server
and with a short WaitTime the same attempt ends as
100 'Conga connection timed out' instead, because Connect gave up before the close
arrived. Either result, with WSUpgradeResponse still '', points at the request
rather than at the network: a header the server dislikes, a sub-protocol or extension
it will not speak, or a path it does not serve WebSockets on.
When a Connection Fails
Connect reports a failure rather than signalling one, so a connection that did not
happen leaves you with a result to interpret and a set of status fields to read. The
fields are described in Status-related fields; this section is
about which of them to look at, and when.
Connected is the dependable test. Connect's rc
is 0 on success, but a handful of validation failures - a URL that cannot be parsed,
headers that cannot be interpreted - currently report the problem in msg while
leaving rc at 0:
ws.URL←'ftp://example.com'
ws.Connect
0 Invalid protocol: ftp
ws.Connected
0
So test Connected (or check that msg is 'Connected') rather than testing rc
alone.
Reading the Message
Failures fall into a few groups, and the message says which:
| Message | What went wrong |
|---|---|
'No URL specified''URL is not a simple character vector''Headers are not character''Improper header format' |
Settings were rejected before anything was attempted |
'Invalid protocol: ...''No host specified''Invalid host/port: ...''Invalid port: ...' |
The URL could not be parsed |
'Could not initialize Conga ...''neither Conga nor DRC were successfully copied' |
Conga could not be located - see Playing Nicely With Others |
'Conga failed to connect to "..." ...' |
The TCP or TLS connection never came up |
'Unexpected server response: ...' |
The server answered with HTTP rather than upgrading |
'Conga connection timed out' |
Nothing arrived within WaitTime |
'Socket closed by server' |
The server closed the connection instead of answering |
'Unexpected ... at ...' |
A hook called from Connect signalled an error |
The connection-level messages carry Conga's own text, which is usually specific enough to act on:
ws.URL←'wss://no-such-host.invalid' ⋄ ws.Connect
1106 Conga failed to connect to "no-such-host.invalid": ERR_INVALID_HOST Host identification not resolved
ws.URL←'ws://127.0.0.1:9' ⋄ ws.Connect
1111 Conga failed to connect to "127.0.0.1": ERR_CONNECT_DATA Unable to connect to host data port
ws.URL←'wss://expired.badssl.com' ⋄ ws.SSLFlags←0 ⋄ ws.Connect
1202 Conga failed to connect to "expired.badssl.com": ERR_INVALID_PEER_CERTIFICATE Remote certificate is invalid
ERR_INVALID_HOST is a name that did not resolve, ERR_CONNECT_DATA a host that
resolved but refused the connection, and ERR_INVALID_PEER_CERTIFICATE a certificate
that failed the validation asked for by
SSLFlags - see Secure Connections.
Where to Look Next
Which field holds the detail depends on how far the attempt got:
| Symptom | Look at |
|---|---|
| The server answered with HTTP | HttpStatus, HttpMessage, HttpHeaders |
| Conga could not parse the response as HTTP | Data, which holds the unparsed event data |
| The handshake completed but something about it was wrong | WSUpgradeResponse |
| The connection ended up somewhere unexpected | Redirections |
| A proxy is in use | ProxyResponse - see When the Proxy Refuses |
| The connection was made and then died | ErrorInfo and LastWaitResponse |
A worked example of the third row - a server answering 200 OK to an upgrade request
because the path serves ordinary HTTP - looks like this:
ws.URL←'wss://example.com'
ws.Connect
¯1 Unexpected server response: 200 OK
ws.(HttpStatus HttpMessage)
200 OK
Nothing is cleared when a connection ends, so all of these survive for as long as you
need them; Connect clears them only when it is about to make a fresh attempt.
Turning Off the Safety Net
Debug has two useful values while diagnosing:
1disables the error trapping everywhere, so an error insideConnect, inside the listener, or inside one of your hooks suspends where it happened instead of being reduced tomsgorErrorInfo. This is how to develop a hook.2stopsConnectjust before the Conga client is created, which is the moment to inspect the headers, secure parameters, and options that are about to be used.
Debug is a shared field, so setting it affects every instance.
Advanced Usage
Partial Messages
Partial Messages
A WebSocket message does not have to arrive, or be sent, in one piece. The protocol
allows a message to be split into a sequence of fragments, each carried in its own
frame, with only the last one marked as "final". WebSocketClient exposes this
directly rather than hiding it: the OnWSReceive
hook is called once per segment, and Send lets you mark a
message as incomplete.
A message arrives in pieces only because the sender chose to fragment it - a common way to stream a large or open-ended payload without having to know its length up front. Fragmentation is a decision made by whoever sent the message, so whether you ever see a partial one depends entirely on the server you are talking to.
Receiving Partial Messages
OnWSReceive is called once per segment, with
MsgState as its right argument:
MsgState.bufferis everything received for this message so far, including the segment that has just arrived.MsgState.payloadis that segment on its own.MsgState.finalis1if the message is now complete,0if more is coming.MsgState.opcodeis the message's type -1for text,2for binary - taken from its first frame.
Reassembly is done for you, so a hook that wants nothing but whole messages only has
to wait for final and read buffer:
∇ client OnMessage state
[1] ⍝ ignore everything until the message is complete
[2] :If state.final
[3] Handle state.buffer
[4] :EndIf
∇
Each client instance has its own MsgState, so several connections running at once
need no special handling - each accumulates independently.
Consuming a message as it arrives
The reason to look at the non-final segments is to avoid holding a large message in
memory, or to start work on it before it has finished arriving. A hook can deal with
each segment and then empty the buffer; WebSocketClient appends the next segment to
whatever it finds there, so draining it keeps the message from accumulating:
∇ client OnChunk state
[1] ⍝ deal with each segment as it arrives rather than buffering the whole message
[2] Received+←≢state.buffer ⍝ ... or write it out, feed a parser, and so on
[3] state.buffer←0⍴state.buffer ⍝ drop what we have dealt with
[4] :If state.final
[5] ⎕←'message complete - ',(⍕Received),' elements'
[6] Received←0
[7] :EndIf
∇
Read buffer rather than payload here: after the first drain the two are the same,
but on any segment you have not drained, buffer is the part you still owe work.
Emptying it with 0⍴ rather than '' preserves the datatype, so the technique works
for binary messages as well as text.
Know Your Server
Character payloads are translated from UTF-8 as each segment arrives, before it is added to the buffer. A server is permitted by RFC 6455 to split a text message in the middle of a multi-byte character, and if one does, that translation fails with a
DOMAIN ERROR, which ends the listener and leaves the error inErrorInfo- see Two things to be careful about in the Usage Guide. In practice servers fragment text on character boundaries; if you are dealing with one that does not, have the server send binary (opcode2) messages and do the UTF-8 translation yourself once the message has been reassembled.
Sending Partial Messages
Send takes an optional second element saying whether the data completes the message:
ws.Send data ⍝ a complete message - final defaults to 1
ws.Send data final ⍝ final←0 leaves the message open
Sending final←0 leaves the message open; each subsequent Send appends another
fragment, and the one you send with final←1 closes it. WebSocketClient tracks this
for you and sets the WebSocket opcodes itself - the first fragment is sent as text or
binary, and the rest as continuations - so all you have to supply is the data and the
flag.
To stream a large payload in fixed-size chunks:
∇ (rc msg)←ws SendChunked payload;size;chunk
[1] ⍝ send payload as a series of fragments of at most 32768 elements
[2] (rc msg)←0 ''
[3] size←32768
[4] :While size<≢payload ⍝ more than one fragment still to go?
[5] (chunk payload)←(size↑payload)(size↓payload)
[6] :If 0≠⊃(rc msg)←ws.Send chunk 0 ⍝ 0 - the message continues
[7] {}ws.Send''1 ⍝ abandon the message
[8] :Return
[9] :EndIf
[10] :EndWhile
[11] (rc msg)←ws.Send payload 1 ⍝ 1 - the last fragment
∇
Check the result of every fragment. Unlike Connect and Close, Send does not
leave its result in the instance's rc and msg fields, so the returned (rc msg) is
the only report you get. A failure partway through leaves the message half-sent.
All fragments of a message must be the same datatype. Character data is sent as a
text message and integer data as a binary one; mixing them within a message is
rejected with 'Datatype is not the same as previous fragment (...)' and nothing is
sent. Note that you do not need to UTF-8 encode character data yourself - Conga does
that as it sends each fragment.
Nothing else can go out on that connection until the message is closed. A
fragmented message owns the connection from its first fragment to its last: anything
else you send in between becomes part of it. In particular, Send keeps its
fragmentation state on the instance, so two threads sending on the same client at the
same time will interleave into a single garbled message. If your application sends
from more than one thread, serialize the sends - or give each thread its own client
instance.
Close out a message you have abandoned. If you stop partway through - because a
fragment failed, or because the data ran out - the instance still believes a message is
open, and the next Send will be treated as a continuation of it. Sending an empty
final fragment ends the message and clears that state. The empty fragment still has to
match the datatype of the fragments already sent, so use '' to close out a text
message and ⍬ to close out a binary one:
ws.Send ''1 ⍝ end an abandoned text message
ws.Send ⍬ 1 ⍝ end an abandoned binary message
Secure Connections
Secure Connections
A WebSocket connection is secured exactly as an HTTPS one is: TLS is negotiated first,
and the handshake and every frame after it travel inside it. WebSocketClient treats a
connection as secure if any of the following is true:
URLbeginswss:orhttps:URLspecifies port443and no scheme at allCertorPublicCertFileis set, whatever the URL says
In the ordinary case - a public server, no client certificate - there is nothing to
configure. WebSocketClient builds an anonymous certificate for the connection, and
wss:// is all you need:
ws←WebSocketClient.New 'wss://echo.websocket.org'
ws.Connect
0 Connected
The Default Does Not Validate the Server
SSLFlags defaults to 32, which tells Conga to accept
the server's certificate without checking it. The connection is encrypted, but nothing
establishes that the server on the other end is the one you meant to reach - an
expired, self-signed, or wrong-host certificate is accepted just as readily as a good
one:
ws←WebSocketClient.New 'wss://expired.badssl.com'
ws.Connect ⍝ TLS succeeded; only the upgrade was refused
¯1 Unexpected server response: 200 OK
Setting SSLFlags to 0 asks for full validation, and the same connection is then
refused where it should be:
ws.SSLFlags←0
ws.Connect
1202 Conga failed to connect to "expired.badssl.com": ERR_INVALID_PEER_CERTIFICATE Remote certificate is invalid 1026
An application that cares who it is talking to should set SSLFlags←0 and keep it
there. The permissive default exists because it lets test and development servers with
self-signed certificates work out of the box; the individual flag values, and how to
relax validation in a narrower way than "accept anything", are in the Conga User
Guide. Priority similarly passes a GnuTLS priority
string straight through, for restricting the protocol versions and ciphers offered.
Presenting a Client Certificate
Servers that authenticate clients by certificate rather than by password need one supplied. There are three equivalent ways to do it:
ws.Cert←cert ⍝ an X509Cert instance you already have
ws.Cert←'client.pem' 'client.key' ⍝ public and private key files
ws.(PublicCertFile PrivateKeyFile)←'client.pem' 'client.key'
The last two are the same thing said differently - a 2-element Cert is simply
shorthand for the two fields. Both files are required: supplying one without the other
fails with 'PublicCertFile is empty' or 'PrivateKeyFile is empty', a file that is
not there gives 'Not found PublicCertFile "..."', and one that cannot be read as a
certificate gives 'Unable to decode PublicCertFile "..." as certificate'. All of
these come back from Connect as an rc of ¯1 before any connection is attempted.
Because setting Cert or PublicCertFile makes the connection secure on its own, a
client certificate cannot be presented over a ws: connection by accident.
Inspecting the Server's Certificate
Once a secure connection is up, PeerCert holds the
server's certificate as an X509Cert instance:
ws.PeerCert.Formatted.Subject
CN=echo.websocket.org
ws.PeerCert.Formatted.(ValidFrom ValidTo)
Formatted also carries Issuer, SerialNo, KeyLength, Extensions and the rest -
see the Conga User Guide for what an X509Cert offers. This is worth reading in an
OnWSUpgrade hook if you want to pin a
certificate or check an issuer yourself: the hook can refuse the connection by
returning a non-zero rc, though note that by then the TLS session is already
established.
PeerCert stays '' on an insecure connection, and when a proxy is in use it holds
the end server's certificate rather than the proxy's - see
The Two Connections Are Secured Separately.
Conga Usage
Conga Usage
Conga is a shared resource. It's not unusual for an application to have more than one Conga-using component like WebSocketClient, HttpCommand, Jarvis, isolate and so on. Each of these needs to use Conga without treading on the others.
Use Conga instead of DRC
If you have more than one Conga-using component in your application, you should use the
Conganamespace (from the conga workspace) in preference to theDRCnamespace. TheConganamespace supports multiple Conga roots which is what you should use in this type of situation. TheDRCnamespace is kept largely for backwards compatibility for older applications.
WebSocketClient uses three user-settable fields that allow you to configure how it locates Conga: LDRC, CongaRef and CongaPath. There is also a fourth, read-only, field - CongaVersion that reports Conga's version once it has been located and initialized.
All of these fields are shared, class-level, fields, so however many instances you create, they resolve Conga once between them.
The resolution is done under :Hold, so instances started on separate threads at the same time cannot race each other into initializing Conga twice.
Default Behavior
Connect initializes Conga on first use, and Init does
the same thing without connecting - useful for checking the setup, or the Conga
version, up front:
ws←WebSocketClient.New ''
ws.Init
0 Initialized
ws.CongaVersion
3 6 1703
ws.LDRC ⍝ reference to the initialized Conga library
#.WebSocketClient.[LIB]
The default behavior for Connect or Init in a workspace that doesn't already have Conga, is to copy the Conga namespace from the conga workspace into the WebSocketClient class and run Conga.Init 'WebSocketClient' thereby creating a Conga root named WebSocketClient, and LDRC is a reference to the initialized Conga library.
Playing Nicely With Others
When your application has more than one Conga-using component, you'll want to use the Conga namespace from conga workspace to create a Conga root for each component. HttpCommand, Jarvis, WebSocketClient and WebSocketServer all behave similarly in their use of LDRC, CongaRef, and CongaPath. Other tools like isolate will have different ways to specify where to find Conga. Let's suppose you have a hypothetical application that uses Jarvis as a web service, HttpCommand to access resources on the net, WebSocketClient for real-time full-duplex communications, and isolate to run computations in parallel.
⍝ get all the components we'll be using
'Conga' ⎕CY 'conga'
'isolate' 'll' ⎕CY 'isolate'
]load HttpCommand -nol
]get https://raw.githubusercontent.com/Dyalog/Jarvis/refs/heads/master/Source/Jarvis.dyalog
]get https://raw.githubusercontent.com/Dyalog/WebSocketClient/refs/heads/master/Source/WebSocketClient.aplc
⍝ tell each component which Conga to use
(HttpCommand Jarvis WebSocketClient).CongaRef←#.Conga
⍝ isolate is configured through isolate.Config rather than a field - left at its
⍝ default it creates its own root, named "isolate", in #.DRC
⍝ run each of the components (this will initialize Conga for each one)
HttpCommand.Get 'dyalog.com'
[rc: 0 | msg: | HTTP Status: 200 "OK" | ≢Data: 22860]
⍳ ll.Each 3 4 5 ⍝ isolate's "parallel each"
1 2 3 1 2 3 4 1 2 3 4 5
Jarvis.Run''
2026-09-09 @ 14.27.17.142 - Starting Jarvis 1.22.6
2026-09-09 @ 14.27.17.145 - Local Conga v3.7 reference is #.[LIB]
2026-09-09 @ 14.27.17.147 - Jarvis starting in "JSON" mode on port 8080
2026-09-09 @ 14.27.17.150 - Serving code in #
2026-09-09 @ 14.27.17.151 - Click http://192.168.223.117:8080 to access web interface
#.[Jarvis] 0 Server started
ws←WebSocketClient.New 'wss://echo.websocket.org'
ws.Connect
0 Connected
>>> Request served by 4d896d95b55478
⍝ check all the Conga roots created
((Jarvis HttpCommand WebSocketClient).LDRC #.DRC).RootName
Jarvis HttpCommand WebSocketClient isolate
As you can see, each component has its own Conga root which can be manipulated independently of the other components. isolate is the odd one out: it takes its Conga through isolate.Config 'drc' ⍵ rather than a field, and expects an already-initialized instance. Left at its default it copies Conga into # and runs Conga.Init 'isolate' itself.
Finding Nemo Conga
WebSocketClient attempts to locate Conga as follows:
- If
LDRCis already set and usable, nothing further happens. CongaRef, if you have set it. It accepts a reference to aCongaorDRCnamespace, a reference to an already-initialized Conga instance (whatConga.Initreturns), or a character vector naming one, such as'#.Conga'. IfCongaRefis set and cannot be resolved, initialization fails rather than falling through to the searches below.- A
CongaorDRCnamespace in##or#. The namespace containing the class is searched before the root, andCongais searched beforeDRC. - The
congaworkspace.Conga(thenDRC) is copied into the class itself, so#.WebSocketClient.Congais where a copied Conga ends up. IfCongaPathis set, only that folder is used and a path that does not exist or is not a folder is reported as such; otherwise thews/folder of the Dyalog installation is tried, and then the current working directory.
CongaPath is not only used for the workspace copy in
step 4. Whenever WebSocketClient initializes a Conga or DRC namespace itself - on
any of the routes above - CongaPath is passed to Init as the location of Conga's
shared libraries, so it applies however Conga was found. It is ignored only when
CongaRef is an already-initialized Conga instance, which
brings its own libraries with it.
Whichever route succeeds, CongaVersion is set from the resolved library, and
LDRC is left pointing at the Conga LIB instance (or the DRC namespace).
Conga Version Requirements
CongaVersion is set once initialization succeeds, and
Init is a convenient way to read it before attempting a
connection. Most of WebSocketClient works with any Conga version that supports
WebSockets; connecting through a proxy additionally requires Conga
3.4.1626 or later, and Connect stops with 'Conga version 3.4.1626 or later is
required to use a proxy' on anything older.
For what Conga itself offers - including the SSLValidation flags and X509Cert - see
the Conga User Guide.
Headers and Authentication
Headers and Authentication
The WebSocket upgrade request is an HTTP request, and anything an HTTP request can carry, it can carry - which is how most servers expect to be told who you are.
Building Headers
Headers is held as a 2-column matrix of name and
value, but it accepts several shapes and normalizes whatever you give it the first
time it is used:
ws.Headers←'Accept: text/plain',(⎕UCS 10),'X-Trace: 42' ⍝ text, one per line
ws.Headers←('Accept' 'text/plain')('X-Trace' '42') ⍝ name/value pairs
ws.Headers←'Accept' 'text/plain' 'X-Trace' '42' ⍝ flat, alternating
ws.Headers←2 2⍴'Accept' 'text/plain' 'X-Trace' '42' ⍝ the matrix itself
All four produce the same thing. A shape that cannot be read as headers signals
FORMAT ERROR from the header methods, and stops Connect with a msg of
'Improper header format'.
In practice the methods are easier than the field:
AddHeader adds a header unless it is already there,
SetHeader overwrites,
RemoveHeader deletes, and
GetHeader reads:
'Accept'ws.AddHeader'text/plain'
'Accept'ws.AddHeader'application/json' ⍝ ignored - Accept is already set
'Accept'ws.SetHeader'application/json' ⍝ this one replaces it
ws.GetHeader'accept'
application/json
Names are matched case-insensitively throughout, so the case you use to look one up or
remove it does not matter - though SetHeader stores the name as you spell it.
Two things happen to your headers on the way out, both covered under
WebSocket Handshake: headers with empty values are dropped, and
Conga adds the protocol's own (Upgrade, Connection, Sec-WebSocket-Key,
Sec-WebSocket-Version), which you should not set yourself.
Identifying Yourself to the Server
There are three ways to supply credentials, and if more than one is used they take precedence in this order:
AuthwithAuthType- an
Authorizationheader you set yourself - credentials embedded in the URL -
wss://userid:password@host
For Basic authentication, give Auth the pair and let WebSocketClient do the
encoding. It recognises (userid password), or a single string containing a colon,
and - if AuthType is empty or 'BASIC' - Base64-encodes it and sets AuthType to
'Basic' for you:
ws.Auth←'brian' 'secret'
ws.Connect
0 Connected
ws.AuthType ⍝ filled in by Connect
Basic
For token schemes, set both fields and nothing is transformed - the header value is
simply AuthType, a space, and Auth:
ws.(Auth AuthType)←'eyJhbGciOi...' 'Bearer'
The Authorization header built this way is added to the request, not to Headers,
so it never appears in the instance's own header matrix.
Keeping Credentials Out of Your Source
Two settings help with the awkward fact that credentials tend to end up in code and in displays.
HeaderSubstitution sets a pair of
delimiters; any delimited name found in a header - or in Auth, which becomes one -
is replaced with the value of that environment variable as the request is built:
ws.HeaderSubstitution←'${' '}'
ws.(AuthType Auth)←'Bearer' '${MY_API_TOKEN}'
The token itself lives in the environment, and the substitution happens too late for
the real value ever to be stored in the instance. A name with no matching environment
variable is left alone rather than blanked, which is worth remembering when a server
rejects credentials that look right: ws.GetEnv 'MY_API_TOKEN' tells you whether the
variable is actually set.
Secret, which defaults to 1, masks Auth and
ProxyAuth in Config and hides Authorization and
Proxy-Authorization values wherever headers are displayed:
ws.Config
...
Auth >>> Secret setting is 1 <<<
...
Set it to 0 while debugging if you need to see what is actually being sent.
Connecting Through a Proxy
Connecting Through a Proxy Server
Many networks do not let an application open an outbound connection directly, and
require it to go through an HTTP proxy instead. WebSocketClient supports this by
tunnelling: it connects to the proxy and asks it, with an HTTP CONNECT request, to
open a connection to the real server on your behalf, and then runs the entire
WebSocket conversation through the tunnel the proxy hands back.
Setting ProxyURL turns tunnelling on. All the other proxy-related settings are ignored if
ProxyURL is empty.
ws←WebSocketClient.New ''
ws.(URL OnWSReceive)←'wss://echo.websocket.org' 'OnMessage'
ws.ProxyURL←'http://proxy.example.com:8080'
ws.Connect
0 Connected
Proxy support relies on Conga version 3.4.1626 or later.
What Connect Does Differently
When ProxyURL is set, Connect inserts four steps ahead of the handshake it would
otherwise perform:
- It connects to the host and port in
ProxyURLrather than the one inURL, using TLS ifProxyURLishttps:. - It sends
CONNECT host:port HTTP/1.1- the host and port taken fromURL- along withProxyHeadersand anyProxy-Authorizationheader it has derived from your settings. - It waits for the proxy's reply and records it in
ProxyResponse. Anything other than a status of'200'ends the attempt. - If
URLis secure, it starts TLS with the end server over the established tunnel.
From there the WebSocket upgrade request goes out exactly as it would have done on a
direct connection, and once the handshake completes the proxy is invisible - Send,
Close, and your OnWSReceive hook all behave identically.
Give URL a scheme when you are proxying. While a direct (non-proxied) connection may not require a wss: or ws: scheme for WebSocketClient to connect, when using a proxy, be sure to supply the scheme in URL.
Authenticating With the Proxy
Proxy credentials are separate from the credentials the end server sees:
ProxyAuth and
ProxyAuthType build the Proxy-Authorization
header on the CONNECT request, while Auth and
AuthType build the Authorization header on the
WebSocket upgrade request inside the tunnel. Setting one has no effect on the other,
and an application talking to an authenticated server through an authenticated proxy
sets all four.
There are three ways to specify credentials for the proxy. If you specify credentials in more than one way, the order of precedence is as follows:
- Setting
ProxyAuthandProxyAuthTypetakes precedence over - Setting a
Proxy-Authorizationheader inProxyHeaderswhich takes precedence over - Supplying credentials in the
ProxyURL.
ProxyAuth follows the same rules as Auth: given (userid password), or a string
containing a :, with ProxyAuthType either empty or case-insensitively matching 'basic', the credentials are
Base64-encoded for you and ProxyAuthType becomes 'Basic'.
ws.ProxyURL←'http://proxy.example.com:8080'
ws.ProxyAuth←'proxyuser' 'proxypassword' ⍝ encoded for you as Basic
Credentials can also be embedded in ProxyURL itself, which is convenient when the
proxy address arrives from a configuration file or an environment variable:
ws.ProxyURL←'http://proxyuser:proxypassword@proxy.example.com:8080'
The two are not additive. If ProxyAuth is set it wins, and credentials in the URL
are ignored; the URL form is used only when ProxyAuth is empty. A
Proxy-Authorization header you place in ProxyHeaders yourself is likewise
overwritten by ProxyAuth, though it survives if only the URL form is present.
Both settings that keep credentials out of your source apply to the proxy as well as to the end server:
HeaderSubstitutionis applied to theCONNECTrequest's headers, including theProxy-Authorizationheader built fromProxyAuth, so a proxy credential can be held in an environment variable:
ws.HeaderSubstitution←'${' '}'
ws.(ProxyAuthType ProxyAuth)←'BEARER' '${PROXY_TOKEN}'
Substitution happens as the request is sent, but the automatic Basic encoding
happens earlier, when the header is built - so ('${USER}' '${PASS}') would encode
the placeholders rather than the values. Use the single-string form, as above, or
do the lookup yourself with GetEnv.
SecretmasksProxyAuthalongsideAuthinConfig, and masks theProxy-Authorizationheader wherever headers are displayed.
The Two Connections Are Secured Separately
There are two independent hops when you tunnel, and it is worth being clear about which settings apply to which:
| Hop | Secured when | Certificate settings used |
|---|---|---|
| You → proxy | ProxyURL begins https: |
None - an anonymous client certificate |
| You → end server | URL is secure, or Cert/PublicCertFile is set |
Cert, SSLFlags, Priority, PublicCertFile, PrivateKeyFile |
So a client certificate is presented to the end server, never to the proxy; there is
currently no way to authenticate to a proxy with a certificate rather than with
ProxyAuth. Errors raised while securing the proxy hop are prefixed PROXY: to
distinguish them from the end-server ones.
In practice most proxies are addressed as plain http: even when the target is
wss:, and that is not the weakness it looks like: a CONNECT tunnel is opaque, so
the TLS session is negotiated end-to-end with the real server through it and the proxy
sees only encrypted bytes. https: on ProxyURL encrypts the CONNECT request
itself - which matters mainly because that request carries your proxy credentials.
When the Proxy Refuses
A proxy that declines to open the tunnel answers the CONNECT request with an
ordinary HTTP response, and Connect leaves the whole of it in ProxyResponse for
you to look at. This is the first place to check whenever a proxied connection fails
where a direct one would have worked:
ws.Connect
¯1 Proxy CONNECT response failed, ProxyResponse has the response from the proxy server
ws.ProxyResponse.(status message)
407 Proxy Authentication Required
ws.ProxyResponse.headers ws.GetHeader 'Proxy-Authenticate'
Basic realm="corporate-proxy"
407 means the credentials were missing, wrong, or in a scheme the proxy does not
accept - and, as above, the Proxy-Authenticate header tells you which scheme it
wants. 403 usually means the credentials were fine but the proxy's policy forbids
the destination host or port; many proxies allow CONNECT only to port 443, which
is a common reason for an otherwise correct ws:// connection to be refused where
wss:// succeeds.
Some proxies explain themselves in the response body. When the reply carries a
Content-Length, Connect reads on for that body and leaves what it gets in
ProxyResponse.payload - as the enclosed Conga event rather than as text, so the body
itself is 4⊃⊃ws.ProxyResponse.payload.
ProxyResponse is populated whenever the proxy replies at all, successful or not, so
it is also available for inspection after a connection that worked. It stays '' if
no reply ever arrived.
The proxy gets one second to reply. The wait for the CONNECT response is fixed
at 1000 ms and is not governed by WaitTime. A proxy
that authenticates against a slow directory service can exceed it, giving
'Proxy CONNECT wait failed: ...' or, if it answers with something unexpected,
'Proxy CONNECT did not respond with HTTPHeader event: ...'. Both leave ProxyResponse
unset, which distinguishes them from a proxy that answered and said no.
Note also that redirections are followed inside the tunnel. If the end server
redirects, Connect returns to the start and dials the proxy again for the new URL,
issuing a fresh CONNECT each time - so MaxRedirections bounds the number of
CONNECT requests as well.
Reference
Settings
Connect-related settings
Connect-related fields
URL
| Description | The WebSocket (or HTTP) server URL to connect to. |
| Default | '' |
| Example(s) | ws.URL←'wss://echo.websocket.org' |
| Details | URL must be a non-empty simple character vector. The scheme may be ws:, wss:, http:, https:, or omitted. If omitted, WebSocketClient will wss:/https: (or a bare port of 443) causes the connection to be treated as secure. On a redirect, URL is updated to the response's Location header value and the prior URL and response details are recorded in Redirections. |
Params
| Description | Request parameters to be appended to the URL's query string. |
| Default | '' |
| Example(s) | ws.Params←'name' 'fred' 'type' 'student' |
| Details | Params may be a simple character vector, a flat array of name/value pairs, or a namespace of variables. It is then properly formatted, if necessary, and appended to any query string already present in URL. |
ValidFormUrlEncodedChars
| Description | A shared, read-only constant listing the characters that may appear in a query string without being percent-encoded. |
| Default | '&=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~*+~%' |
| Example(s) | WebSocketClient.ValidFormUrlEncodedChars |
| Details | When Params is a simple character vector, Connect treats it as already encoded and leaves it alone if every character it contains is in this set; otherwise it is passed through UrlEncode. Params given as pairs or as a namespace is always encoded. The field is read-only and is omitted from Config. |
Headers
| Description | HTTP headers to be sent in the WebSocket upgrade request. Headers can be a 2-column matrix of name/value pairs, a vector of name/value pairs ('hdr1' 'value1' 'hdr2' 'value2') or a vector of pairs of names/values (('hdr1' 'value1')('hdr2' 'value2')). |
| Default | 0 2⍴⊂'' |
| Example(s) | ws.Headers←('X-Custom-Header' 'value') ('Accept' '*/*') |
| Details | Headers is usually more conveniently maintained with AddHeader, SetHeader, RemoveHeader, and GetHeader rather than being set directly. Headers with empty values are dropped before the upgrade request is sent, and any header set via Auth/AuthType, Protocol, or Extensions is merged in alongside those already in Headers. |
Auth
| Description | Credentials to authenticate with: one of '' (none), a token character vector, or a 2-element vector (userid password). |
| Default | '' |
| Example(s) | ws.Auth←'myuserid' 'mypassword' |
| Details | If Auth is set, it takes priority over an Authorization header set directly, which in turn takes priority over credentials embedded in the URL (wss://userid:password@host). If Auth is (userid password) or contains a :, and AuthType is '' or 'BASIC' (case-insensitive), the credentials are Base64-encoded automatically and AuthType is set to 'Basic'. |
AuthType
| Description | The authentication scheme used to build the Authorization header, along with Auth. |
| Default | '' |
| Example(s) | ws.AuthType←'BEARER' |
| Details | Typical values are '', 'BASIC', 'BEARER', 'TOKEN'. AuthType and Auth are combined as 'AuthType Auth' to form the Authorization header value - see Auth above for the automatic Basic-auth encoding rules. |
Origin
| Description | The intended value of the Origin header. |
| Default | 'null' |
| Details | In the current implementation, Origin is not automatically added to the request headers by Connect. If a server requires an Origin header, set it explicitly with AddHeader/SetHeader (or via Headers). |
HeaderSubstitution
| Description | A 2-element vector of (beg end) delimiter strings used to substitute environment variable values into header names/values. |
| Default | '' |
| Example(s) | ws.HeaderSubstitution←'${' '}'ws.AuthType←'BEARERws.Auth←'${MY_TOKEN}' |
| Details | When non-empty, any header text matching beg, followed by a letter and any further characters, followed by end, is treated as the name of an environment variable; that portion of the header is replaced with the variable's value (via GetEnv) before the WebSocket upgrade request is sent. If the environment variable is not set, the matched text is left unchanged. This lets secrets be kept out of source code. |
Secret
| Description | Whether Config should hide credential values. |
| Default | 1 |
| Example(s) | ws.Secret←0 ⋄ ws.Config |
| Details | When 1, Config replaces the Auth and ProxyAuth values in its result with '>>> Secret setting is 1 <<<' so credentials aren't inadvertently displayed or logged. Set to 0 to see the actual values in Config's result. |
AutoUpgrade
| Description | Whether to automatically accept the server's WebSocket upgrade response. |
| Default | 1 |
| Example(s) | ws.AutoUpgrade←0 |
| Details | When 1, Conga's WSAutoUpgrade option is set so the handshake completes without user intervention (OnWSUpgrade is still called, as a last chance to validate/reject it). When 0, the server's response is left as a normal HTTP response for inspection via onWSResponse, and WSAccept is called to manually complete the handshake. When connecting through a proxy, the WSAutoUpgrade option is instead applied to the tunnelled connection after the proxy CONNECT/TLS handshake completes. |
MaxRedirections
| Description | The maximum number of redirect "hops" that Connect will follow. |
| Default | 2 |
| Example(s) | ws.MaxRedirections←5 |
| Details | If the server responds with an HTTP redirection status (301, 302, 303, 307, or 308) and a Location header, Connect follows it, recording each hop as a namespace appended to Redirections. Connect fails once the number of redirections would exceed MaxRedirections. |
Debug
| Description | Whether to disable error trapping so that errors suspend where they occur, rather than being reported as a return code and message. |
| Default | 0 |
| Example(s) | ws.Debug←1 ⍝ let errors suspendws.Debug←2 ⍝ also stop just before the Conga client is created |
| Details | Debug is a shared field - setting it on any instance, or on the class itself (WebSocketClient.Debug←1), affects every instance. When 0, Connect and New trap all errors: Connect returns rc of ¯1 and a msg of 'Unexpected ...' naming the error and the line it occurred on, and a failed New returns a namespace with rc, msg, Connected, and URL in place of an instance. Any non-zero value disables that trapping, so the error suspends and can be examined in the debugger. |
| Note | Debug←2 additionally stops just before the Conga client is created, displaying Stopped for debugging... (Press Ctrl-Enter) - useful for inspecting Secure, Host, Port, Path, and the headers that Connect has built. Because errors in a hook function called from Connect are otherwise trapped and reduced to a message, setting Debug to 1 is the usual way to debug one. |
Proxy-related settings
These fields are only used if ProxyURL is set to connect through a proxy server. Note that when using a proxy server, URL must be fully qualified with a leading scheme (ws://, wss://, http:// or https://).
ProxyURL
| Description | The address of an HTTP proxy server to tunnel the WebSocket connection through. |
| Default | '' |
| Example(s) | ws.ProxyURL←'http://proxy.example.com:8080' |
| Details | If non-empty, Connect first connects to ProxyURL and issues an HTTP CONNECT request for URL's host and port, then continues the WebSocket handshake over the resulting tunnel (upgrading it to TLS first if URL is secure). Using a proxy requires Conga version 3.4.1626 or later - Connect fails immediately if CongaVersion is older. |
ProxyAuth
| Description | Credentials to authenticate with the proxy server: one of '' (none), a token character vector, or a 2-element vector (userid password). |
| Default | '' |
| Example(s) | ws.ProxyAuth←'proxyuser' 'proxypassword' |
| Details | Combined with ProxyAuthType to build the Proxy-Authorization header sent with the CONNECT request, following the same automatic Basic-encoding rules as Auth. If ProxyAuth is not set, credentials embedded in ProxyURL (e.g. http://user:pass@proxyhost) are used instead. |
ProxyAuthType
| Description | The authentication scheme used to build the Proxy-Authorization header, along with ProxyAuth. |
| Default | '' |
| Example(s) | ws.ProxyAuthType←'BASIC' |
| Details | Works the same way as AuthType, but applies to the proxy CONNECT request rather than the WebSocket upgrade request. |
ProxyHeaders
| Description | HTTP headers to be sent with the proxy CONNECT request. Accepts the same formats as Headers. |
| Default | 0 2⍴⊂'' |
| Example(s) | ws.ProxyHeaders←('Proxy-Connection' 'Keep-Alive') |
| Details | ProxyHeaders is merged with any Proxy-Authorization header derived from ProxyAuth/ProxyAuthType (or from credentials embedded in ProxyURL) before Connect issues the CONNECT request. |
ProxyResponse
| Description | A namespace holding the proxy server's response to the CONNECT request. |
| Default | '' |
| Example(s) | ws.ProxyResponse.status ws.ProxyResponse.headers ws.GetHeader 'Content-Type' |
| Details | Populated whenever the proxy replies to CONNECT, whether or not the request succeeds - it is cleared at the start of each Connect, so it remains '' if the current attempt received no response. The namespace has the elements version, status, message, headers, and payload; version, status, message and headers are character data, status being the HTTP status as text, for example '200'. payload is '' unless the response carries a Content-Length greater than 0, in which case Connect waits for the body and encloses the resulting Conga event into it - the body text itself is 4⊃⊃ProxyResponse.payload. If status is not '200', Connect fails with msg of 'Proxy CONNECT response failed, ProxyResponse has the response from the proxy server'. |
Conga-related settings
Conga-related fields
BufferSize
| Description | The buffer size (in bytes) passed to Conga's Clt (client) constructor for the underlying connection. |
| Default | 200000 |
| Example(s) | ws.BufferSize←1000000 |
| Details | The connection is created in Conga's 'http' mode, where BufferSize limits the size of the HTTP headers Conga will accept - in practice, the headers of the server's response to the WebSocket upgrade request. BufferSize must be set before Connect is called; changing it afterwards has no effect on an already-established connection. |
| Note | BufferSize does not limit or divide up WebSocket messages. A message arrives in several pieces only if the sender fragmented it - see Partial Messages - and raising BufferSize will not change how a message is delivered. |
WaitTime
| Description | The timeout, in milliseconds, passed to Conga's Wait function while listening for WebSocket events, and used to bound how long Close waits for the listener thread to terminate. |
| Default | 5000 |
| Example(s) | ws.WaitTime←10000 ⍝ wait up to 10 seconds |
| Details | Each iteration of the listen loop calls LDRC.Wait with a timeout of WaitTime milliseconds; a timeout is not treated as an error and the loop simply waits again. Close signals the listener to stop and then polls for up to WaitTime×1.1 milliseconds before forcibly killing the listener thread. |
Cert
| Description | An X509Cert instance to use for the client certificate when connecting over wss (HTTPS/TLS). If empty, PublicCertFile and PrivateKeyFile are used instead. |
| Default | ⍬ |
| Example(s) | ws.Cert←cert ⍝ cert is a previously-created X509Cert instance |
| Details | Cert may also be set to a 2-element vector (PublicCertFile PrivateKeyFile) as a shorthand for setting those two fields directly. Supplying either Cert or PublicCertFile causes the connection to be treated as secure even if the URL scheme does not indicate it. |
SSLFlags
| Description | The SSL/TLS validation flags passed to Conga as SSLValidation when creating the secure connection. |
| Default | 32 (accept the server certificate without checking it) |
| Example(s) | ws.SSLFlags←0 ⍝ perform full certificate validation |
| Details | See the Conga User Guide for the full list of SSLValidation flag values and how they may be combined. |
Priority
| Description | The GnuTLS priority string passed to Conga when creating the secure connection. |
| Default | 'NORMAL:!CTYPE-OPENPGP' |
| Example(s) | ws.Priority←'NORMAL:!CTYPE-OPENPGP' |
| Details | See the Conga User Guide and GnuTLS documentation for the syntax of priority strings. |
PublicCertFile
| Description | Path to a file containing the client's public certificate, used when Cert is not set to an X509Cert instance. |
| Default | '' |
| Example(s) | ws.PublicCertFile←'client.pem' |
| Details | If PublicCertFile is supplied, PrivateKeyFile must be supplied as well (and vice versa) - WebSocketClient reads and decodes the certificate from these two files to build the X509Cert instance used for the connection. |
PrivateKeyFile
| Description | Path to the file containing the private key corresponding to PublicCertFile. |
| Default | '' |
| Example(s) | ws.PrivateKeyFile←'client.key' |
| Details | Used together with PublicCertFile; see PublicCertFile above. |
LDRC
| Description | A shared reference to the Conga (or DRC) namespace/instance that WebSocketClient uses to make all Conga calls, set once Conga has been located and initialized. |
| Default | unset |
| Example(s) | ws.LDRC.Names'.' |
| Details | LDRC is set automatically during Initialize and should not normally be set directly by user code. It is shared across all instances of WebSocketClient. However, it can be used to call Conga functions that aren't otherwise exposed through the WebSocketClient API. |
CongaPath
| Description | A shared field giving the path to a user-supplied Conga workspace and the platform-specific shared libraries that go with it. |
| Default | '' |
| Example(s) | WebSocketClient.CongaPath←'/opt/conga/' |
| Details | CongaPath serves two purposes. When Conga has to be copied from a workspace, it names the folder to copy from - and only that folder is searched; if CongaPath is empty, WebSocketClient falls back to the Dyalog installation's ws/ folder and then the current working directory. CongaPath is also passed to Init whenever WebSocketClient initializes a Conga or DRC namespace itself, telling Conga where to load its shared libraries from, so it applies however Conga was located - not just when one is copied. It is ignored only when CongaRef is an already-initialized Conga instance. See Finding Conga. |
CongaRef
| Description | A shared field letting the user supply a specific reference to a Conga or DRC library, instead of WebSocketClient locating and/or copying one itself. |
| Default | '' |
| Example(s) | WebSocketClient.CongaRef←#.Utils.Conga ⍝ Conga is a reference to an already-initialized Conga namespace |
| Details | CongaRef may be a character vector naming a namespace, a reference to the Conga or DRC namespace, or a reference to an already-initialized Conga instance. See Finding Conga for more information. |
CongaVersion
| Description | A shared, read-mostly field set to the Conga library version once Conga has been initialized. |
| Default | '' |
| Details | CongaVersion is set from LDRC.Version during Initialize and is subsequently used internally - for example, to check that the Conga version in use is recent enough to support proxy connections. |
Event Hook settings
"Hook" Functions
A hook lets your application replace WebSocketClient's default behavior for a particular WebSocket event. Each field holds the name of an APL function, as a character vector - not a reference to one. Setting a hook to '' (the default) restores the built-in behavior for that event.
The name is resolved when the event occurs, not when the field is set, by evaluating it in ## - the namespace in which the WebSocketClient class resides. The function must therefore be visible from there; a name like 'utils.OnMessage' is resolved relative to ## as well. A name that cannot be found produces a VALUE ERROR at event time.
All hook functions are called dyadically, with client as the left argument - a reference to the WebSocketClient instance itself, passed so that the hook function has access to the instance's public fields and methods. The right argument is the data associated with the Conga event. All hooks except OnWSReceive return a 2-element (rc msg) result, where rc of 0 means "carry on".
OnWSUpgrade and OnWSResponse are called on the thread that called Connect, and Connect traps errors - a ⎕SIGNAL or unexpected error inside either hook makes Connect return rc of ¯1 and msg of 'Unexpected ... ' rather than suspending. OnWSReceive, OnClose, and OnError are called on the listener thread (see ListenerThread), whose wait loop is wrapped in a single error trap. An error in any of the three does not suspend the thread, but it does end the listener: ⎕DMX is recorded in ErrorInfo, the connection is closed, and Connected is set to 0. A hook that needs to survive a message it cannot handle must therefore trap the error itself. Set Debug to 1 while developing a hook to disable trapping everywhere, so that errors suspend where they occur rather than being reduced to a field.
OnWSUpgrade
| Description | The name of a function to be called when AutoUpgrade is 1 and the server has returned a WebSocket upgrade response, giving the application a chance to validate or reject the upgrade before Connect completes. |
| Default | '' |
| Example(s) | ws.OnWSUpgrade←'OnUpgrade' |
| Signature | `` |
| Details | Called as (rc msg)←client OnWSUpgrade WSUpgradeResponse. The right argument is the namespace described under WSUpgradeResponse - WebSocketClient parses the server's raw response before calling the hook, so version, status, message, headers, and payload are already split out. Return rc as 0 to accept the upgrade; return a non-zero rc and a useful msg to reject it, in which case Connect closes the connection, terminates the listener thread, sets Connected to 0, and returns the hook's rc and msg. If OnWSUpgrade is not set, the upgrade is accepted unconditionally. |
| Note | Because AutoUpgrade is 1, the WebSocket is already upgraded when OnWSUpgrade is called. Returning a non-0 return code will close the connection. |
OnWSResponse
| Description | The name of a function to be called instead of OnWSUpgrade when AutoUpgrade is 0, giving the application a chance to inspect the server's (unaccepted) HTTP response before the handshake is completed. |
| Default | '' |
| Example(s) | ws.OnWSResponse←'OnResponse' |
| Signature | `` |
| Details | Called as (rc msg)←client OnWSResponse WSUpgradeResponse, with the same, already-parsed right argument as OnWSUpgrade. Return 0 to let WebSocketClient perform the WSAccept call - if Conga rejects it, Connect fails with Conga's return code and a msg of 'Conga WSAccept failed: ...'. Return any non-zero rc (with a useful msg) to reject the connection. If OnWSResponse is not set, the WSAccept call is performed by default. |
| Note | Unlike OnWSUpgrade, the WebSocket is not connected when OnWSResponse is called. Returning a non-0 return code will close the connection without having upgraded it to a WebSocket connection. Leave the WSAccept call to WebSocketClient - performing it in the hook and then returning 0 causes it to be issued twice. |
OnWSReceive
| Description | The name of a function to be called for each received WebSocket message segment, replacing the default behavior of displaying complete messages in the session prefixed by >>>. |
| Default | '' |
| Example(s) | ws.OnWSReceive←'OnMessage' |
| Signature | `` |
| Details | Called as client OnWSReceive MsgState, where MsgState is the namespace in which WebSocketClient assembles the message currently being received - buffer is everything received so far, payload is just this segment, final says whether the message is now complete, and opcode is 1 for a text message or 2 for a binary one. Character data is decoded from UTF-8 before the hook is called. The hook returns no result. If OnWSReceive is not set, WebSocketClient displays each complete message in the session with the prefix >>>. |
| Note | The hook is called for every segment, not only the last one, so a hook that wants whole messages must test MsgState.final and ignore the rest. Reassembly itself is done for you - MsgState.buffer already holds the complete message when final is 1. |
| Note | MsgState is a single namespace reused for every message, and WebSocketClient clears it as soon as the hook returns for a final segment. Take a copy of anything the hook needs to keep or hand to another thread; retaining the reference itself will not work.As MsgState is a namespace, you can also stash any additional message-related information within. |
OnClose
| Description | The name of a function to be called when the WebSocket is closed by the server or the network, allowing the application to supply its own return code and message for the close event. |
| Default | '' |
| Example(s) | ws.OnClose←'OnClosed' |
| Signature | `` |
| Details | Called as (rc msg)←client OnClose waitData. waitData is the result of Conga's Wait function and is a 4-element array of [1] the Conga return code, [2] the Conga object name of the connection, [3] the event ('Close'), [4] data, if any - the same value left in LastWaitResponse. Once the hook returns, the listener closes the Conga connection and terminates, and Connected is set to 0. If OnClose is not set, the default result is 0 'WebSocket Closed'. |
| Note | OnClose reports a close initiated by the other end. Calling Close yourself signals the listener to stop directly, so OnClose is not called in that case. |
OnError
| Description | The name of a function to be called when Conga reports a WebSocket error while listening. |
| Default | '' |
| Example(s) | ws.OnError←'OnErr' |
| Signature | `` |
| Details | Called as (rc msg)←client OnError waitData. waitData is the result of Conga's Wait function and is a 4-element array of [1] the Conga return code, [2] the Conga object name of the connection, [3] the event ('Error'), [4] data, if any. As with OnClose, the listener closes the connection and terminates once the hook returns. If OnError is not set, the default result is 0 'WebSocket Error: ',⍕waitData. |
| Note | OnError covers errors reported on an established WebSocket. Errors raised while connecting are reported through Connect's own rc and msg instead. |
WebSocket-specific settings
These settings relate to the WebSocket protocol itself - the two Sec-WebSocket- headers that shape the upgrade request, and the parsed upgrade response that the server sends back.
Protocol
| Description | The value(s) for the Sec-WebSocket-Protocol header, used to request one or more application sub-protocols from the server. |
| Default | '' |
| Example(s) | ws.Protocol←'chat, superchat' |
| Details | If non-empty, Connect merges Protocol into the request headers (via the same add-unless-already-defined logic as AddHeader) when building the WebSocket upgrade request; leading, trailing, and redundant embedded blanks are removed first. Setting a Sec-WebSocket-Protocol header directly via Headers/AddHeader/SetHeader takes priority over Protocol. Protocol may also be supplied as the fourth element of the constructor's argument vector - ⎕NEW WebSocketClient (url onWSReceive onWSUpgrade protocol). |
| Note | The server selects at most one of the offered sub-protocols and names it in the Sec-WebSocket-Protocol header of its response - inspect WSUpgradeResponse.headers (or GetHeader in a hook function) to find out which, if any, was accepted. |
Extensions
| Description | The value(s) for the Sec-WebSocket-Extensions header, used to request WebSocket extensions (such as permessage-deflate) from the server. |
| Default | '' |
| Example(s) | ws.Extensions←'permessage-deflate' |
| Details | If non-empty, Connect merges Extensions into the request headers the same way as Protocol when building the WebSocket upgrade request. Setting a Sec-WebSocket-Extensions header directly via Headers/AddHeader/SetHeader takes priority over Extensions. |
| Note | Requesting an extension does not implement it - WebSocketClient neither negotiates nor applies extension semantics itself. Only offer an extension if the application is prepared to deal with the messages the server then sends. |
WSUpgradeResponse
| Description | A namespace holding the parsed server response to the WebSocket upgrade request. |
| Default | '' |
| Example(s) | ws.WSUpgradeResponse.status ws.WSUpgradeResponse.headers ws.GetHeader 'Sec-WebSocket-Protocol' |
| Details | Set whenever the server responds to the upgrade request, whether AutoUpgrade is 1 or 0, and regardless of whether OnWSUpgrade/OnWSResponse is set. It is cleared, like the status fields, at the start of each Connect, so it remains '' if the current attempt received no upgrade response. The namespace has the elements version, status, message, headers, and payload. status is the HTTP status as an integer, for example 101; headers is a 2-column matrix of header names and values, suitable as the left argument to GetHeader; version, message, and payload are character vectors. |
| Note | WSUpgradeResponse is also the right argument passed to OnWSUpgrade/OnWSResponse, so a hook function does not need to read the field to see the response. It is retained on the instance so the response can still be examined after Connect returns. |
MsgState
| Description | A namespace holding the message currently being received - the buffer in which WebSocketClient reassembles fragmented messages. |
| Default | (buffer:'' ⋄ payload:'' ⋄ final:¯1 ⋄ opcode:¯1) |
| Example(s) | ws.MsgState.buffer ws.MsgState.final |
| Details | A WebSocket message may be sent as a series of fragments, and Conga reports each one separately. WebSocketClient accumulates them here so that hook functions do not have to. The namespace has four elements: buffer is everything received so far for this message, including the current segment; payload is the current segment alone; final is 1 when the segment just received completes the message and 0 when more is coming; and opcode is the message's type, taken from its first frame - 1 for text, 2 for binary. MsgState is the right argument passed to the OnWSReceive hook. |
| Note | The same namespace is reused for every message. Once a message is complete - and, if a hook is set, once that hook has returned - buffer and payload are reset to '' and final and opcode to ¯1. Between messages, therefore, final of ¯1 means "no message in progress". Connect clears the same four elements at the start of each attempt. Copy anything you need to keep rather than retaining the reference. |
Status-related settings
These fields are set by WebSocketClient rather than by you: they report what happened during Connect, what the listener thread is doing, and what the server said. They are public so that they can be read at any time - Connect and Close return the important ones as their result as well, but the fields remain available afterwards, which is where you look when something did not work.
Connect clears every one of them back to its default before it begins, so what you find in them always describes the most recent attempt and never a previous one. Nothing is cleared when a connection ends, so the fields survive for inspection after Close or after the listener has stopped.
Three further fields are cleared by Connect in the same way but are documented alongside the settings they belong with: MsgState, WSUpgradeResponse, and ProxyResponse. The ListenerThread property is described with the public methods.
rc
| Description | The return code of the last Connect or Close. |
| Default | ¯1 |
| Example(s) | :If 0≠ws.rc ⋄ ⎕←ws.msg ⋄ :EndIf |
| Details | 0 means the operation succeeded. Connect sets rc to ¯1 for the failures it detects itself, or to the Conga return code when a Conga call is what failed; Close sets it to 0. Both also return (rc msg) as their result, so assigning that result is usually more convenient than reading the fields. |
| Note | Send does not update rc and msg - its returned (rc msg) is the only report of a failed send. See Sending Partial Messages. |
msg
| Description | The message accompanying rc, explaining what happened. |
| Default | '' |
| Example(s) | ws.msgConnected |
| Details | 'Connected' after a successful Connect and 'Closed' after a successful Close. On failure it describes the problem - for example 'Conga failed to connect to "example.com": ...', or 'Unexpected DOMAIN ERROR at OnUpgrade[4]' when a hook called from Connect signalled an error and Debug is 0. Connect also returns 0 'Already connected' if the instance already has a live connection, in which case nothing is re-negotiated. |
Connected
| Description | Whether the WebSocket is currently connected. |
| Default | 0 |
| Example(s) | :If ws.Connected ⋄ ws.Send data ⋄ :EndIf |
| Details | Set to 1 once the upgrade handshake has completed and the listener thread has been started, and back to 0 by the listener as it terminates - whether it stopped because the server closed the connection, because Close asked it to, or because an error ended it. Connect resets Connected to 0 before each attempt. |
| Note | Connected is the reliable test of whether the WebSocket is usable, but it is the listener that clears it. In the rare case where Close has to ⎕TKILL a listener that did not stop within WaitTime×1.1 milliseconds, the field is left as it stood; ws.ListenerThread∊⎕TNUMS is then the better check. |
Connection
| Description | The name of the Conga object for this connection. |
| Default | '' |
| Example(s) | ws.LDRC.Describe ws.Connection |
| Details | Set when Connect successfully creates the Conga client, and reset to '' by Close and by a failed Connect. It is the handle to pass to Conga's own functions - Describe, GetProp, and so on - if you need to interrogate the connection directly. |
| Note | Unlike the other fields on this page, Connection is read as well as written: Connect checks it to decide whether the instance is already connected, so overwriting it will confuse the instance. |
ErrorInfo
| Description | The ⎕DMX namespace captured when an error terminated the listener thread. |
| Default | '' |
| Example(s) | ws.ErrorInfo.EMDOMAIN ERROR |
| Details | The listener runs inside an error trap, so an error on that thread does not suspend it - the error is recorded here, the connection is closed, Connected is set to 0, and the listener ends. This covers errors in your OnWSReceive, OnClose, and OnError hooks, in the UTF-8 translation of an incoming message, and in Conga itself. ErrorInfo is '' until something is caught, so a listener that stopped with ErrorInfo still '' stopped for an ordinary reason rather than an error. |
| Note | Setting Debug to a non-zero value disables the trap, so that errors suspend the listener thread and can be examined in the debugger instead. See Two things to be careful about. |
LastWaitResponse
| Description | The most recent non-timeout result of Conga's Wait on the listener thread. |
| Default | '' |
| Example(s) | 3⊃ws.LastWaitResponse ⍝ the event name |
| Details | A 4-element vector of the Conga return code, the Conga object name, the event name ('WSReceive', 'Closed', 'Error', ...), and the event's data. It is updated for every event the listener sees except 'Timeout', so it survives as a record of the last thing that actually happened on the connection - most usefully the 'Closed' or 'Error' event that ended it. |
Data
| Description | The unparsed data of a response Conga could not interpret as HTTP. |
| Default | '' |
| Details | Populated only in the specific case where Conga reports an HTTPHeader event whose data it has not been able to break into version, status, message and headers. Connect then fails with rc of ¯1 and msg of 'Conga failed to parse the response HTTP header', leaving the raw data here for inspection. It normally stays ''. |
HttpStatus
| Description | The HTTP status of the last non-WebSocket response received during Connect. |
| Default | ⍬ |
| Example(s) | ws.HttpStatus301 |
| Details | An integer. Set when the server answers the upgrade request with an ordinary HTTP response rather than a 101 - in practice a redirection (301, 302, 303, 307, 308), which Connect follows, or any other status, which fails with 'Unexpected server response: ...'. Connect resets it to ⍬ at the start of each attempt, so ⍬ after a successful connection means the handshake was answered directly. |
| Note | The status of a successful upgrade is not recorded here - it is WSUpgradeResponse.status. |
HttpMessage
| Description | The HTTP reason phrase accompanying HttpStatus. |
| Default | '' |
| Example(s) | ws.HttpMessageMoved Permanently |
| Details | Set and reset alongside HttpStatus. |
HttpVersion
| Description | The HTTP version of the response that set HttpStatus. |
| Default | '' |
| Example(s) | ws.HttpVersionHTTP/1.1 |
| Details | Set and reset alongside HttpStatus. |
HttpHeaders
| Description | The headers of the response that set HttpStatus. |
| Default | '' |
| Example(s) | ws.HttpHeaders ws.GetHeader 'Location' |
| Details | A 2-column matrix of header names and values, suitable as the left argument to GetHeader. Set and reset alongside HttpStatus. |
Redirections
| Description | A vector of namespaces, one per redirection followed during Connect. |
| Default | ⍬ |
| Example(s) | ⊃ws.Redirections.URL ⍝ where we were first sent |
| Details | Each namespace records the state before that redirection was followed: URL is the URL that produced the response, and HttpVersion, HttpStatus, HttpMessage, and HttpHeaders are the response itself. URL is then updated to the Location header and the request retried, up to MaxRedirections times. When a proxy is in use, each redirection also means a fresh CONNECT. |
| Note | Redirections accumulates as an attempt proceeds and is cleared at the start of the next one, so it always describes a single Connect. |
Secure
| Description | Whether the connection to the server is secure. |
| Default | ⍬ |
| Details | Set from the parsed URL during Connect - 1 for wss:/https:, or when a Cert or PublicCertFile has been supplied. Secure, Host, Port, and Path always describe the end server, never the proxy, and are set before the handshake, so they show what Connect was aiming at even when the attempt failed. |
Host
| Description | The host name parsed from URL, lower-cased. |
| Default | '' |
| Example(s) | ws.Hostecho.websocket.org |
| Details | Any credentials and port in the URL are removed; see Secure above. |
Port
| Description | The port parsed from URL, or the default for the scheme. |
| Default | ⍬ |
| Example(s) | ws.Port443 |
| Details | 80 or 443 if the URL did not give a port explicitly. This is the port named in the CONNECT request when connecting through a proxy, which is worth checking if a proxy refuses the tunnel. |
Path
| Description | The resource path parsed from URL. |
| Default | '' |
| Example(s) | ws.URL←'wss://example.com/api/socket'ws.Path/api/socket |
| Details | Always begins with /, and is / if the URL gave no path at all. Spaces are converted to %20. The query string is not included - Connect builds that separately from the URL's own query string and Params, and appends it to Path when it sends the upgrade request. |
PeerCert
| Description | The server's certificate, on a secure connection. |
| Default | '' |
| Example(s) | ws.PeerCert.Formatted |
| Details | Read from Conga once the upgrade handshake has completed, and only when the connection is secure - it stays '' otherwise. When a proxy is in use this is still the end server's certificate, since TLS is negotiated through the tunnel; the proxy's own certificate is not retained. |
Note
Connect clears these fields only when it is going to attempt a connection. If the instance is already connected it returns 0 'Already connected' immediately, leaving every status field as the live connection left it - so a second Connect is safe and does not disturb what you are looking at.
Public Methods
WebSocketClient exposes two kinds of method. Instance methods are called on a client instance (ws.Connect) and act on that instance's fields. Shared methods are called on the class itself (WebSocketClient.Base64Encode), though they can also be called through an instance; they do not depend on any instance's state.
Most methods return a 2-element (rc msg) result, where rc of 0 means success and msg describes what happened. Connect and Close also leave their result in the instance's rc and msg fields; Send does not.
Operational Methods
New
| Description | Shared method to create a new WebSocketClient instance. |
| Syntax | ws←WebSocketClient.New args |
args |
can be any of
|
ws |
the new WebSocketClient instance, or, if construction failed, a namespace reporting the failure |
| Example(s) | ws←WebSocketClient.New (URL:'echo.websocket.org' ⋄ OnWSReceive:'OnMessage')ws←WebSocketClient.New 'echo.websocket.org' 'OnMessage'ws←WebSocketClient.New '' ⋄ ws.(URL OnWSReceive)←'echo.websocket.org' 'OnMessage' |
| Details | args may be empty (resulting in an instance with all defaults), a namespace whose variables are the settings to apply, or a vector of positional settings in the order URL, OnWSReceive, OnWSUpgrade, Protocol, Headers, Params. A namespace containing a name that is not a public field signals an error reporting the invalid setting(s). |
| Note | New traps errors that ⎕NEW would otherwise signal: if construction fails it returns a namespace with rc, msg, Connected, and URL in place of an instance, so check Connected or rc rather than assuming an instance came back. Set Debug to 1 to have the error signalled instead. |
Connect
| Description | Establish the connection and complete the WebSocket handshake. |
| Syntax | (rc msg)←ws.Connect |
rc |
0 if the WebSocket is connected, non-zero otherwise |
msg |
'Connected', 'Already connected', or a description of the failure |
| Example(s) | ws←WebSocketClient.New (URL:'echo.websocket.org')ws.Connect |
| Details | Connect resolves and initializes Conga if that has not happened yet, builds and sends the WebSocket upgrade request from URL, Params, Headers, and the other connect-related settings, follows up to MaxRedirections redirects, and - via OnWSUpgrade or OnWSResponse - completes the handshake. On success it starts the listener thread, sets Connected to 1, and returns 0 'Connected'. If the instance already has a live connection, it returns 0 'Already connected' without doing anything. |
| Note | Errors are trapped and returned as ¯1 and a msg beginning 'Unexpected ' unless Debug is non-zero. On failure the connection is closed, the listener thread is terminated, and Connected is set back to 0. |
Send
| Description | Send a message, or one fragment of a message, over an established WebSocket. |
| Syntax | (rc msg)←ws.Send data(rc msg)←ws.Send (data final) |
data |
character data to send as a text message, or integer data to send as a binary message |
final |
1 (the default) if this completes the message, 0 if further fragments follow |
rc |
0 if the data was sent, non-zero otherwise |
msg |
'' if the data was sent, otherwise a description of the failure |
| Example(s) | ws.Send 'Hello'ws.Send ('First part' 0) ⋄ ws.Send ('last part' 1) |
| Details | When final is 0 the message is left open and subsequent Send calls continue it - WebSocketClient sets the continuation opcode itself. Returns 0 '' on success, ¯1 'No client connection has been established' if there is no connection, or Conga's return code with 'Conga send failure: ...'. |
| Note | All fragments of one message must be of the same datatype - mixing character and integer fragments fails with 'Datatype is not the same as previous fragment (...)'. Unlike Connect and Close, Send does not update the instance's rc and msg fields. |
Close
| Description | Close the WebSocket and stop the listener thread. |
| Syntax | (rc msg)←ws.Close |
rc |
always 0 |
msg |
'Closed', 'Not listening', or 'Already closed' |
| Example(s) | ws.Close |
| Details | Close signals the listener thread to stop and waits up to WaitTime×1.1 milliseconds for it to terminate, killing it with ⎕TKILL if it has not. It then clears Connection and returns 0 'Closed'. If no listener is running it returns 0 'Not listening', and if the connection has already been closed, 0 'Already closed'. |
| Note | Because Close stops the listener directly rather than through a Conga Closed event, the OnClose hook is not called. Expunging the instance does not close the connection: the running listener thread holds a reference to the instance, so the class's destructor does not run and you are left with an orphaned listener on a live connection. Recover the reference with ⎕INSTANCES #.WebSocketClient and call Close on it - see Closing from your side. |
Init
| Description | Locate and initialize Conga without making a connection. |
| Syntax | (rc msg)←ws.Init |
rc |
0 if Conga was initialized, non-zero otherwise |
msg |
'Initialized', or a description of why Conga could not be initialized |
| Example(s) | ws.Init |
| Details | Performs the Conga resolution that Connect would otherwise do on first use, honouring CongaRef and CongaPath, and sets the shared LDRC and CongaVersion fields. Returns 0 'Initialized' on success, or a non-zero rc and a msg describing why Conga could not be initialized. |
| Note | Calling Init is optional - it is useful for checking the Conga setup, or CongaVersion, before attempting a connection. |
Header Manipulation Methods
AddHeader
| Description | Add a header to Headers, unless a header of that name is already defined. |
| Syntax | {hdrs}←{name}ws.AddHeader value{hdrs}←ws.AddHeader (name value) |
name |
the header name; if omitted, it is taken from the first element of the right argument |
value |
the header value; '' adds nothing |
hdrs |
(shy) the updated Headers matrix |
| Example(s) | 'Accept'ws.AddHeader'application/json'ws.AddHeader 'Accept' 'application/json' |
| Details | If a header of that name already exists, AddHeader leaves it alone - use SetHeader to overwrite. Header names are matched case-insensitively. A value of '' adds nothing. The shy result is the updated Headers matrix. |
| Note | Signals a FORMAT ERROR if the current contents of Headers cannot be interpreted as headers. SetHeader and RemoveHeader do the same. |
SetHeader
| Description | Set a header in Headers, overwriting any existing header of that name. |
| Syntax | {hdrs}←{name}ws.SetHeader value{hdrs}←ws.SetHeader (name value) |
name |
the header name; if omitted, it is taken from the first element of the right argument |
value |
the header value |
hdrs |
(shy) the updated Headers matrix |
| Example(s) | 'Accept'ws.SetHeader'text/plain' |
| Details | Behaves like AddHeader except that an existing header of the same name is replaced rather than kept. Header names are matched case-insensitively and the shy result is the updated Headers matrix. |
GetHeader
| Description | Retrieve a header's value from Headers, or from a header matrix supplied as the left argument. |
| Syntax | value←{hdrs}ws.GetHeader name |
hdrs |
a 2-column matrix of headers to search; if omitted, the instance's Headers |
name |
the header name to look up, or a nested vector of header names |
value |
the header value; '' if a single name is not found, or '∘???∘' in place of each name not found when several are requested |
| Example(s) | ws.GetHeader'Accept'ws.WSUpgradeResponse.headers ws.GetHeader 'Sec-WebSocket-Protocol' |
| Details | With no left argument, GetHeader looks in the instance's Headers. Supplying hdrs - for example the headers element of WSUpgradeResponse or ProxyResponse - searches that matrix instead. Names are matched case-insensitively, and a name that is not present returns ''. |
| Note | name may be a nested vector of names, in which case the corresponding values are returned; any name that is not present appears in the result as '∘???∘' rather than being dropped. |
RemoveHeader
| Description | Remove one or more headers from Headers. |
| Syntax | {hdrs}←ws.RemoveHeader name |
name |
the header name to remove, or a nested vector of header names |
hdrs |
(shy) the updated Headers matrix |
| Example(s) | ws.RemoveHeader'Accept'ws.RemoveHeader 'Accept' 'User-Agent' |
| Details | Removes every header whose name matches, case-insensitively. Removing a name that is not present is not an error. The shy result is the updated Headers matrix. |
Informational Methods
Config
| Description | Return the instance's current configuration - every public field and its value. |
| Syntax | r←ws.Config |
r |
a 2-column matrix of public field names and their current values |
| Example(s) | ws.Configws.Secret←0 ⋄ ws.Config |
| Details | r is a 2-column matrix of field names and values, covering both instance and shared fields (ValidFormUrlEncodedChars is omitted). A field that cannot be read is shown as 'not set'. |
| Note | While Secret is 1 - the default - the Auth and ProxyAuth values are replaced with '>>> Secret setting is 1 <<<' so that credentials aren't inadvertently displayed or logged. |
Version
| Description | Shared method returning the name, version number, and date of this WebSocketClient. |
| Syntax | r←WebSocketClient.Version |
r |
a 3-element vector of the name, version number, and date |
| Example(s) | WebSocketClient.Version |
| Details | r is a 3-element vector, for example 'WebSocketClient' '0.9.0' '2026-08-24'. |
Documentation
| Description | Shared method returning a pointer to this documentation. |
| Syntax | r←WebSocketClient.Documentation |
r |
a character vector naming the documentation website |
| Example(s) | WebSocketClient.Documentation |
| Details | Returns the character vector 'See https://dyalog.github.io/WebSocketClient/'. |
ListenerThread
| Description | A read-only property giving the thread number of the listener thread started by Connect. |
| Syntax | r←ws.ListenerThread |
r |
the thread number of the listener, or ⍬ if no listener is running |
| Example(s) | ws.ListenerThread∊⎕TNUMS ⍝ is the listener still running? |
| Details | ⍬ before Connect succeeds and after Close has terminated the listener. The listener thread is where OnWSReceive, OnClose, and OnError are called. |
Utility Methods
GetEnv
| Description | Shared method returning the value of an environment variable. |
| Syntax | r←WebSocketClient.GetEnv var |
var |
the name of an environment variable |
r |
the variable's value, or '' if it is not set |
| Example(s) | WebSocketClient.GetEnv'DYALOG' |
| Details | Returns '' if the variable is not set. This is the same lookup used by HeaderSubstitution to substitute environment variables into header values. |
Base64Encode
| Description | Shared method to Base64-encode data. |
| Syntax | r←{cpo}WebSocketClient.Base64Encode w |
cpo |
"code points only" - if supplied (with any value), character data is encoded as code points rather than being translated to UTF-8 |
w |
the character or integer data to encode |
r |
the Base64-encoded character vector |
| Example(s) | WebSocketClient.Base64Encode'userid:password' |
| Details | Character data is translated to UTF-8 before encoding; integer data is encoded as-is. Supplying any left argument (cpo, "code points only") suppresses the UTF-8 translation. |
| Note | Auth is Base64-encoded automatically when it holds (userid password) or a userid:password string - see Auth. |
Base64Decode
| Description | Shared method to decode Base64-encoded data. |
| Syntax | r←{cpo}WebSocketClient.Base64Decode w |
cpo |
"code points only" - if supplied (with any value), the decoded bytes are returned as code points rather than being translated from UTF-8 |
w |
the Base64-encoded character vector to decode |
r |
the decoded data |
| Example(s) | WebSocketClient.Base64Decode'dXNlcmlkOnBhc3N3b3Jk' |
| Details | The decoded bytes are translated from UTF-8 unless a left argument (cpo) is supplied, in which case they are treated as code points. |
UrlEncode
| Description | Shared method to URL-encode data for use in a query string. |
| Syntax | r←{name}WebSocketClient.UrlEncode data |
name |
the name to pair data with, when data is a single value |
data |
a character vector, an even number of name/value character vectors, a vector of name/value pairs, or a namespace of variables to encode |
r |
the URL-encoded character vector, for example 'name=fred&type=student' |
| Example(s) | WebSocketClient.UrlEncode 'name' 'fred' 'type' 'student''name'WebSocketClient.UrlEncode'fred' |
| Details | data may be a simple character vector, an even number of name/value character vectors, a vector of name/value pairs, or a namespace whose variables are the names and values. The result is a character vector such as 'name=fred&type=student', with anything outside the unreserved character set percent-encoded from its UTF-8 bytes. |
| Note | Connect applies this to Params itself, so UrlEncode only needs to be called directly when building a query string by hand. |
setDisplayFormat
| Description | Refresh the ⎕DF display form of an instance. |
| Syntax | ws.setDisplayFormat ns |
ns |
the instance (or namespace) whose display form is to be set - normally the instance itself |
| Example(s) | ws.setDisplayFormat ws |
| Details | Sets ⎕DF to a summary of rc, msg, and the connection state, which is what you see when you display an instance:[ rc: 0 | msg: Connected | Connected to wss://echo.websocket.org ]WebSocketClient calls this itself whenever URL, Connected, rc, or msg changes, so it rarely needs calling directly. It is also used to give the namespace returned by a failed New the same display form as a real instance. |
About
MIT License
Copyright (c) 2026 Dyalog
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Release Notes
Release Notes
1.0.0 - 2026-09-01
Initial release.