Skip to content

Server-Sent Events (SSE) ​

HTTPX implements the WHATWG Server-Sent Events format: httpx.sse.Event.Writer serializes event blocks, and httpx.sse.Event.Parser parses an inbound text/event-stream body.

Server SSE Handler ​

The high-level server buffers each response body, so a route can return a finite batch of events in one response:

zig
const std = @import("std");
const httpx = @import("httpx");

fn liveFeedHandler(ctx: *httpx.router.Context) anyerror!httpx.router.Response {
    var out = std.ArrayList(u8).empty;
    var writer = httpx.sse.Event.Writer.init(ctx.allocator);
    try writer.writeEvent(&out, &.{"Client connected to live feed"}, "connected", 0, null);

    var count: u32 = 1;
    while (count <= 10) : (count += 1) {
        var buf: [64]u8 = undefined;
        const msg = try std.fmt.bufPrint(&buf, "Heartbeat count: {d}", .{count});
        try writer.writeEvent(&out, &.{msg}, "tick", count, null);
    }

    return .{
        .status = 200,
        .body = out.items,
        .contentType = "text/event-stream; charset=utf-8",
        .headers = &.{.{ .name = "Cache-Control", .value = "no-cache" }},
    };
}

pub fn main() !void {
    var gpa: std.heap.DebugAllocator(.{}) = .init;
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    const io = std.Io.Threaded.global_single_threaded.io();

    var server = try httpx.Server.init(allocator, io, .{ .port = 8080 });
    defer server.deinit();
    try server.get("/api/live-feed", liveFeedHandler);
    server.run();
}

Long-lived SSE connections require a custom HTTP/1.1 connection loop built from the socket and protocol primitives; the router cannot keep a response open.

Wire Format ​

SSE transmits text streams with Content-Type: text/event-stream; charset=utf-8:

text
id: 1
event: tick
data: Heartbeat count: 0

id: 2
event: tick
data: Heartbeat count: 1

Released under the MIT License.