websocket(3tcl)

πŸ“¦
version 2.0 Β· 3 versions
, in debian trixie, ubuntu questing, ubuntu resolute, ubuntu stonking

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::open hands 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 message
    • pong β€” the response to an incoming ping message
    • connect β€” notification of a successful connection to the server πŸŽ€
    • disconnect β€” disconnection from the remote end 😭
    • close β€” pending closure of the connection
    • timeout β€” 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 is text, binary, ping or pong, 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 the http::geturl option -protocol is 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. 🌈

  • sock is the listening socket of the websocket compliant server declared using ::websocket::server.
  • path is a glob-style path to match in client request, whenever this will occur.
  • cb is the command to callback (see Callbacks).
  • proto is 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 β€” or name β€” Name or IP of local end.
  • closed β€” 1 if the connection is closed, 0 otherwise.
  • client β€” 1 if the connection is a client websocket, 0 otherwise.
  • server β€” 1 if the connection is a server websocket, 0 otherwise.
  • type β€” server if the connection is a server websocket, client otherwise.
  • 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, -keepalive defaults to 30 seconds, 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 away
  • 1002 β€” Protocol Error
  • 1003 β€” Received incompatible data type
  • 1006 β€” Abnormal closure
  • 1007 β€” Received data not consistent with type
  • 1008 β€” Policy violation
  • 1009 β€” Received message too big
  • 1010 β€” Missing extension
  • 1011 β€” Unexpected condition
  • 1015 β€” 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