Wssup - simple WebSocket client library

wssup - simple WebSocket client library


Hi Ziggit!

A couple of months ago I was writing a NetworkTables client in Zig and needed a WebSocket client library (the standard library only has WebSocket server support).
So I used karlseguin’s websocket.zig, but I didn’t like it because of how bulky it is, and its (over)use of inferred error sets (which made it hard to figure out which errors can be returned from where).

So I wrote my own, small WebSocket client library. It’s just one file, and cloc tells me it’s ~600 lines of code (including tests and excluding comments), and it operates on readers and writers (so you can use it with any transport).
So far it seems to be working out fine for my NetworkTables library, and I’m planning to port my ResoniteLink library to it soon.

Here’s a simple example:

// Perform an HTTP handshake. The reader and writer can come from anywhere -
// TCP stream, Unix socket or from the TLS client.
const handshake = try ws.handshake(reader, writer, &random, "/ws", .{
    .extra_headers = "host: example.org\r\n" ++
        "user-agent: Zig/" ++ builtin.zig_version_string ++ " (wssup)\r\n",
});

if (handshake.result != .ok) {
    // Response head is available in `handshake.head`.
    return std.log.err("http handshake failed ({t})", .{handshake.result});
}

// Send a ping.
try ws.writePingZeroMask(writer, "mic check");

// Iterate over messages.
// Set frame and message limits to prevent denial of service attacks.
var iter: ws.MessageIterator = .init(1024 * 8, 1024 * 1024);
defer iter.deinit(init.gpa);

loop: while (true) {
    // MessageIterator buffers incoming frames to form complete messages.
    const message = try iter.next(init.gpa, reader);
    defer message.deinit(init.gpa);

    switch (message.opcode) {
        .text => {
            std.log.info("got text message: '{f}'", .{ t, std.zig.fmtString(message.data) });
        },
        .connection_close => {
            const close = ws.parseClose(message.data);
            std.log.info("connection closed: {t} '{f}'", .{ close.code, std.zig.fmtString(close.reason) });
            break :loop;
        },
        .ping => {
            std.log.info("got ping", .{});
            try ws.writePongZeroMask(writer, message.data);
        },
        else => {},
    }
}

example.zig in the repo will show you how to use it with Zig’s TLS client and how to generate masking keys.

I’m not a big fan of the WebSocket protocol for various reasons, but this library was really fun to make, and I learned a lot about API design!
It is unlicensed, so feel free to use it anywhere and vendor wssup.zig into your code.

Let me know what you think!

Supported Zig versions

As of writing, 0.16.0 and master are supported.