websocket(3tcl)
websocket β the websocket(3tcl) manual, bestie edition π
okay so. you're here because you want Tcl to do WebSocket things, and honestly? iconic choice. this is the websocket client and server page, and it's long, so we're doing this in parts. buckle up π
β¨ NAME β¨
websocket β a Tcl implementation of the websocket protocol π¦
β¨ SYNOPSIS β¨
package require Tcl 8.6 9
package require http 2.7
package require logger
package require sha1
package require base64
package require websocket ?1.6?
::websocket::open url handler ?options?
::websocket::send sock type ?msg? ?final?
::websocket::server sock
::websocket::live sock path cb ?proto?
::websocket::test srvSock cliSock path ?hdrs? ?qry?
::websocket::upgrade sock
::websocket::takeover sock handler ?server?
::websocket::conninfo sock what
::websocket::find ?host? ?port?
::websocket::configure sock args
::websocket::loglevel ?loglvl?
::websocket::close sock ?code? ?reason?
β¨ DESCRIPTION β¨
NOTE: THIS DOCUMENTATION IS WORK IN PROGRESS... π like, they said it, not me.
so the websocket library is a pure Tcl implementation of the WebSocket specification, and it covers the needs of both clients and servers. no cap. π«Ά
what do websockets even do? okay so imagine a regular HTTP connection but then you upgrade it into a long-lived, continuous binary-or-text conversation between the client and the server. that's the whole vibe. and this library gives you a high-level interface to receive and send data the way RFC 6455 (that's v. 13 of the protocol, bestie) says you should, so you never have to think about protocol framing and reassembly. it's all handled. you're welcome. π
it also implements the ping facility from the standard, plus levers to control it. pings are server-driven and they make sure your connection stays alive even when you're behind home (NAT) networks. π§
and there's a bunch of introspection stuff too β to ask about the current state of the connection, and also to get notifications of incoming pings if you need those. plus a handful of helper procedures so upgrading the handshake inside existing web servers isn't a whole ordeal. πΈ
the main character of this whole library is websocket::takeover. it takes over a regular socket and treats it as a WebSocket β doing all the necessary protocol framing, packetisation and reassembly for servers and clients. it also takes a handler, which is a command that gets called back every time a (possibly reassembled) packet from the remote end is ready to be delivered to the original caller.
now here's the tea π: even though the package exports websocket::takeover, applications seldom call it directly, because the package ships other commands specifically tuned for what clients and servers actually need. so like, don't come for the low-level one unless you know why.
clients π β typically you open a connection to a remote server by handing a WebSocket URL (the ws: or wss: schemes, iykyk) and the handler from above to websocket::open. the opening procedure is a wrapper around the latest http::geturl implementations: it arranges to keep the socket created inside the http library opened for reuse, but confiscates it from its (internal) map of known sockets so it can use it for its own purposes. sneaky? a little. necessary? absolutely. π
servers π¦ β you start by registering yourself through ::websocket::server, plus a bunch of handlers for paths using ::websocket::live. then, for each incoming client connection, you should test the incoming request to detect whether it's an upgrade request using ::websocket::test, and perform the final handshake with ::websocket::upgrade β which places that socket connection under the control of the websocket library and its central procedure. slay.
apart from those main commands, the package gives you a number of commands for introspection and basic operations on the websockets it has under its control. and since WebSocket connections are long-lived, most of your remaining communication with the library happens by way of callbacks β commands triggered whenever important events happen inside the library, but mostly whenever data has been received on a WebSocket. π
β¨ CALLBACKS β¨
a bunch of the library's commands take a handler command as an argument. that handler gets called back when data arrives, but also on important events inside the library, or on events that come from control messages sent by the remote end. for each callback that runs, these arguments get appended: β
sockβ the identifier of the WebSocket, like the one::websocket::openhands you.typeβ a textual type describing the event or the message content. it can be one of these:textβ a complete text message π¬binaryβ a complete binary message π§pingβ an incoming ping messagepongβ the response to an incoming ping messageconnectβ notification of a successful connection to the server πdisconnectβ disconnection from the remote end πcloseβ pending closure of the connectiontimeoutβ notification of a connection timeout πerrorβ notification of an error condition
msgβ holds the data of the message whenever that's relevant, i.e. when the type istext,binary,pingorpong, and whenever there's data available. π
more parts coming, bestie β don't go anywhere π
π¦ API π¦
okay so this is the part where we meet all the actual commands, and honestly? iconic lineup. let's go one by one, no skimming, we're doing the whole thing π
::websocket::open url handler ?options?
This one is your client-side bestie β it opens a WebSocket out to a remote, websocket-enabled HTTP server. The url you hand it has to start with ws: or wss:, which are literally just the WebSocket glow-up versions of http: and https:. β¨
The handler is a command that gets called back on data reception, or any time something important happens over the life of the websocket. ::websocket::open returns a socket, and that socket is doing double duty: it's both the identifier of the websocket and the identifier of the physical low-level socket to the server. You can pass it to a bunch of other commands for introspection or to control how the library behaves. π«Ά
Under the hood it's basically a wrapper around the ::http::geturl command, so it gives you mostly the same set of dash-led options that ::http::geturl does. Below are just the ones that differ, the WebSocket-specific ones:
-headersβ this one is supported, but heads up: a bunch of headers get automatically added internally by the library so it can handshake the upgrade from a regular HTTP socket into a WebSocket with the server. π-validateβ not supported, because it genuinely has no real point for WebSockets.-handlerβ used internally by the websocket library, so you can't touch it. π-commandβ also used internally by the websocket library, also off-limits.-protocolβ this one lets you specify a list of application protocols to handshake with the server. These protocols might help the server trigger application-specific features. Just know that thehttp::geturloption-protocolis used internally by the websocket library and cannot be used. π-timeoutβ supported, but it's implemented as part of the library itself, so that a number of finalising cleanups can happen. πΈ
::websocket::send sock type ?msg? ?final?
Sends a fragment or a control message to the remote end of the WebSocket identified by sock. The type of the message can be an integer according to the specification, or β preferably β one of these case insensitive strings: "text", "binary" or "ping". π¨
The actual content going to the remote end lives in msg. Message fragmentation? Totally doable, just set the final argument to non-true, knowing that the type of each fragment has to be the same. The command returns the number of bytes that were effectively sent, or -1 on errors. π
Serious errors β like when sock doesn't identify a known WebSocket, or when the connection isn't stable yet β will generate errors that must be catched. not optional bestie, catch them.
::websocket::server sock
Registers the (accept) socket sock as the identifier fo an HTTP server that is capable of doing WebSockets. π The paths this server will listen on for incoming connections should be declared using ::websocket::live.
::websocket::live sock path cb ?proto?
Registers callbacks that get performed on a WebSocket compliant server β one registered with ::websocket::server β whenever a client connects to a matching path and protocol. π
sockis the listening socket of the websocket compliant server declared using::websocket::server.pathis a glob-style path to match in client request, whenever this will occur.cbis the command to callback (see Callbacks).protois a glob-style protocol name matcher.
::websocket::test srvSock cliSock path ?hdrs? ?qry?
Tests whether the connection from an incoming client on socket cliSock and on the path path is the opening of a WebSocket stream within a known server srvSock. π
Here's the cute part: the incoming request is not upgraded at once. Instead, a (temporary) context for the incoming connection gets created, which lets server code perform a number of actions, if necessary, before the WebSocket stream connection actually goes live. π¦
The text is made by analysing the content of the headers hdrs, which should contain a dictionary list of the HTTP headers of the incoming client connection. The command returns 1 if this is an incoming WebSocket upgrade request and 0 otherwise.
::websocket::upgrade sock
Upgrades the socket sock β the one that ::websocket::test already deemed to be a WebSocket connection request β into a true WebSocket as recognised by this library. β¨
As a result, the necessary connection handshake gets sent to the client, and the command arranges for the relevant callbacks to be made during the life of the WebSocket, notably using the specifications described by ::websocket::live.
::websocket::takeover sock handler ?server?
Takes over the existing opened socket sock to implement sending and receiving WebSocket framing on top of it. π«Ά
The procedure arranges for handler to be called back whenever messages, control messages, or other important internal events are received or occured. server defaults to 0, and you can set it to 1 (or any boolean that evaluates to true) to say "yep, this is a WebSocket within a server."
Protocol specifics aside, servers should ping their clients at regular intervals to keep the connection opened at all time. So when server is true, the library will arrange to send those pings automatically. π
::websocket::conninfo sock what
Your introspection plug β gives callers some semi-internal info about an existing websocket connection. π Depending on what you pass as what, you get back:
peernameβ Name (preferred) or IP of remote end.socknameβ ornameβ Name or IP of local end.closedβ1if the connection is closed,0otherwise.clientβ1if the connection is a client websocket,0otherwise.serverβ1if the connection is a server websocket,0otherwise.typeβserverif the connection is a server websocket,clientotherwise.handlerβ The handler command associated to the websocket.stateβ The state of the websocket, which can be one of:CONNECTINGβ Connection to remote end is in progress.CONNECTEDβ Connection is connected to remote end.CLOSEDβ Connection is closed.
::websocket::find ?host? ?port?
Look among existing websocket connections for the ones that match the hostname and port number filters you pass as parameters. π
This lookup takes the remote end into account, and the host argument gets matched both against the hostname (whenever available) and the IP address of the remote end. Both host and port are glob-style string matching filters and default to *, i.e. they'll match any host and/or port number. π
::websocket::configure sock args
Takes a bunch of dash-led options (and their values) to configure how an existing websocket connection behaves. π The recognised options are these (and yes, they can be shortened to the lowest common denominator):
-keepaliveβ the number of seconds between each keepalive pings being sent along the connection. A zero or negative number will effectively turn off the feature. In servers,-keepalivedefaults to30seconds, and in clients, no pings are initiated. β°-pingβ the text that's used during the automated pings. This text defaults to the empty string, which leads to an empty ping.
::websocket::loglevel ?loglvl?
Set or query the log level of the library, which defaults to error. π Logging is built on top of the logger module, and the library makes use of these levels: debug, info, notice, warn and error.
Call it with no argument and this procedure simply returns the current log level. Otherwise, loglvl should hold the log level you want. β¨
::websocket::close sock ?code? ?reason?
Gracefully closes a websocket that was directly or indirectly passed to ::websocket::takeover. ποΈ
The procedure will optionally send the code and a describing reason as part of the closure handshake. Good defaults are provided, so reasons for a number of known codes will be sent back. And lowkey important: only the first 125 characters of a reason string will be kept and sent as part of the handshake. βοΈ
The known codes are:
1000β Normal closure (the default code when none provided).1001β Endpoint going away1002β Protocol Error1003β Received incompatible data type1006β Abnormal closure1007β Received data not consistent with type1008β Policy violation1009β Received message too big1010β Missing extension1011β Unexpected condition1015β TLS handshake error
π EXAMPLES π
Okay bestie, here's a whole lil client script that actually works. It opens a websocket connection to an echo service, waits 400ms to make sure the connection is really established, then yeets a single textual message over there and waits for the echo service to bounce it right back. β¨
Real talk though: a real example would probably use the connect callback to know when connection to the remote server has been establish and would only send data at that time. This one's just doing the impatient thing with after. And then finally the script closes the connection. Slay.
package require websocket
::websocket::loglevel debug
proc handler { sock type msg } {
switch -glob -nocase -- $type {
co* {
puts "Connected on $sock"
}
te* {
puts "RECEIVED: $msg"
}
cl* -
dis* {
}
}
}
proc test { sock } {
puts "[::websocket::conninfo $sock type] from [::websocket::conninfo $sock sockname] to [::websocket::conninfo $sock peername]"
::websocket::send $sock text "Testing, testing..."
after 2000 ::websocket::close $sock
}
set sock [::websocket::open ws://ws.ifelse.io/ handler]
after 400 test $sock
vwait forever
And if you want the other side of the story β the server side β example code for a websocket server is provided in the Tcllib directory examples/websocket. Go peek, it's giving homework-but-cute. π¦
π TLS SECURITY CONSIDERATIONS π
So this package uses the TLS package to handle the security for https urls and other socket connections. That's the whole security department, right there. π«‘
Now here's the thing, and it's important: policy decisions like the set of protocols to support and what ciphers to use are not the responsibility of TLS, nor of this package itself however. Such decisions are the responsibility of whichever application is using the package, and are likely influenced by the set of servers the application will talk to as well. So likeβ¦ don't come for TLS, it's not its job. π
For example, in light of the recent POODLE attack [http://googleonlinesecurity.blogspot.co.uk/2014/10/this-poodle-bites-exploiting-ssl-30.html] discovered by Google, many servers will disable support for the SSLv3 protocol. To handle this change the applications using TLS must be patched, and not this package, nor TLS itself. Such a patch may be as simple as generally activating tls1 support, as shown in the example below. π
package require tls
tls::init -tls1 1 ;# forcibly activate support for the TLS1 protocol
... your own application code ...
Note the ;# forcibly activate support for the TLS1 protocol comment β that's verbatim, bestie, that's the point. Your app does the patching. Not us. π
π BUGS, IDEAS, FEEDBACK π
Okay so, gentle truth time: this document, and the package it describes, will undoubtedly contain bugs and other problems. π No cap. Please report such in the category websocket of the Tcllib Trackers [http://core.tcl.tk/tcllib/reportlist]. Please also report any ideas for enhancements you may have for either package and/or documentation. We want the ideas, we want the chaos, we want the glow-up. π«Ά
When proposing code changes, please provide unified diffs, i.e the output of diff -u. That's the love language here.
Note further that attachments are strongly preferred over inlined patches. Attachments can be made by going to the Edit form of the ticket immediately after its creation, and then using the left-most button in the secondary navigation bar. Periodt. π
π SEE ALSO π
http
β KEYWORDS β
http, internet, net, rfc 6455
π CATEGORY π
Networking
And that's a wrap on websocket(3tcl) from the tcllib crew, version 1.6 β tysm for reading all three parts, you're genuinely the main character of this manpage. Go build something iconic with it, bestie. πβ¨π
π¦ where this page comes from
- debian trixie: tcllib 2.0+dfsg-4 Β· Tcl
- ubuntu questing: tcllib 2.0+dfsg-4
- ubuntu resolute: tcllib 2.0+dfsg-5 Β· Tcl
- ubuntu stonking: tcllib 2.0+dfsg-6 Β· Tcl
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms