Low-allocation RFC 6455 websocket primitives for Zig with a specialized frame hot path, strict handshake validation, and permessage-deflate.
- 🧱 RFC 6455 core: frame parsing, masking, fragmentation, ping/pong, close handling, and server handshake validation.
- 🏎 Tight hot path:
Conn(comptime static)specializes role and policy at comptime to keep runtime branches out of the core path. - 📦 Low-allocation reads: stream frames chunk-by-chunk, read full frames, or borrow buffered payload slices when they fit.
- 🧠 Strict protocol checks: rejects malformed control frames, invalid close payloads, bad UTF-8, bad mask bits, and non-minimal extended lengths.
- 🗜
permessage-deflate: handshake negotiation plus compressed message read/write support, withserver_no_context_takeoverandclient_no_context_takeover. - 🧠 Optional context takeover: compile-time
ConnTypetoggle (permessage_deflate_context_takeover) enables cross-message compression state when negotiated. - 🎛 Per-message compression policy: compile-time
ConnTypeknobs decide when messages are compressed (permessage_deflate_min_payload_len,permessage_deflate_require_compression_gain). - ⏱ Timeout hooks: optional read, write, and flush time budgets with typed runtime hooks for framework-owned transports.
- 👀 Observability hooks: optional typed event stream for handshakes, frame/message flow, control frames, protocol failures, and timeout events.
- 🔁 Convenience helpers:
readMessage,echoFrame,writeText,writeBinary,writePing,writePong, andwriteClose. - 🧪 Validation stack: unit tests, fuzz/property tests, a cross-library interop matrix, soak runners, and benchmarks live alongside the library.
After you have already accepted the websocket upgrade and have a reader/writer pair:
const std = @import("std");
const zws = @import("zwebsocket");
fn runEcho(reader: *std.Io.Reader, writer: *std.Io.Writer) !void {
var conn = zws.ServerConn.init(reader, writer, .{});
var scratch: [4096]u8 = undefined;
while (true) {
_ = conn.echoFrame(scratch[0..]) catch |err| switch (err) {
error.ConnectionClosed => break,
else => |e| return e,
};
try conn.flush();
}
}For explicit handshake validation on a raw stream:
const accepted = try zws.acceptServerHandshake(req, .{});
try zws.writeServerHandshakeResponse(writer, accepted);For a full standalone echo server example:
zig build examples -Dexample=echo-server -- --port=9001 --compressionFor a frame-oriented echo server that stays on echoFrame(...):
zig build examples -Dexample=frame-echo-server -- --port=9002For a simple websocket client that performs the HTTP upgrade and then uses zws.ClientConn:
zig build examples -Dexample=client -- --host=127.0.0.1 --port=9001 --message=helloAdd as a dependency:
zig fetch --save <git-or-tarball-url>build.zig:
const zws_dep = b.dependency("zwebsocket", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("zwebsocket", zws_dep.module("zwebsocket"));zws.ConnType(.{ ... })creates a websocket connection type specialized for a fixed role and policy set.zws.Conn,zws.ServerConn, andzws.ClientConnare the common aliases.- Low-level read path:
beginFrame,readFrameChunk,readFrameAll,discardFrame,readFrameBorrowed. - Convenience read path:
readFrame,readMessage,echoFrame. - Write path:
writeFrame,writeText,writeBinary,writePing,writePong,writeClose,flush. - Handshake path:
computeAcceptKey,acceptServerHandshake,writeServerHandshakeResponse,serverHandshake. - Compression path:
PerMessageDeflate,PerMessageDeflateConfig,ServerHandshakeResponse.permessage_deflate,Config.permessage_deflate. - Runtime hooks:
TimeoutConfig,DefaultRuntimeHooks,ConnTypeWithHooks(...),acceptServerHandshakeWithHooks(...),serverHandshakeWithHooks(...),ObserveEvent.
DOCUMENTATION.md: API stability, transport/runtime expectations, deployment notes, and validation entry points.
src/root.zig: public package surfacesrc/conn.zig: connection state machine and frame I/Osrc/handshake.zig: server handshake validation and response generationsrc/extensions.zig: extension negotiation helpersbenchmark/bench.zig: benchmark clientbenchmark/zwebsocket_server.zig: standalone benchmark serverexamples/echo_server.zig: standalone echo server exampleexamples/frame_echo_server.zig: frame-level echo server example usingechoFrameexamples/ws_client.zig: standalone client example with manual HTTP upgradevalidation/: Zig interop and soak runners, plus peer dependency metadata
Benchmark support lives under benchmark/.
zig build bench-compare -Doptimize=ReleaseFastEnvironment overrides:
CONNS=16 ITERS=200000 WARMUP=10000 MSG_SIZE=16 zig build bench-compare -Doptimize=ReleaseFastFor benchmark details, see benchmark/README.md.
zig build test
zig build interop
zig build soak
zig build validate
zig build examples
zig build bench-compare -Doptimize=ReleaseFastzwebsocket is intentionally focused on a small websocket core.
- Server-side RFC 6455 handshake validation is included.
- Connection state is synchronous and stream-oriented.
permessage-deflateis implemented and negotiated when enabled.- Compression is disabled by default (
Config.permessage_deflate = null). - Even when negotiated, outgoing compression is opt-in (
PerMessageDeflateConfig.compress_outgoing = falseby default). - Context takeover support is disabled by default (
StaticConfig.permessage_deflate_context_takeover = false). - When enabled,
StaticConfigdefaults (permessage_deflate_min_payload_len = 64,permessage_deflate_require_compression_gain = true) skip tiny messages and avoid non-beneficial compression. - No TLS or HTTP server framework is bundled; use the raw stream API or the example server as the integration point.
permessage-deflateframing is implemented withstd.compress.flate(both non-takeover and optional context-takeover paths).