NAME

Punk::SSE - a Server-Sent Events stream

SYNOPSIS

# in the app
sse '/events' => 'Live#feed';

# MyApp::Controller::Live
sub feed {
    my ($c, $stream) = @_;                 # the socket is ours now

    my $tick;
    $tick = sub {
        return unless $stream->is_open;
        $stream->send({ time => time });
        $c->timer(1)->on_done($tick);      # push once a second
    };
    $tick->();

    $stream->on(close => sub { warn "client gone\n" });
}

DESCRIPTION

An sse route streams text/event-stream to a browser's EventSource: one-directional, plain HTTP, with the client reconnecting on its own. The handler for an sse route is called with the Punk::Context and a stream once Punk has taken the socket over, and pushes events onto it; the stream then lives on the worker's event loop with no worker pinned per connection.

Three transports carry it, chosen per request: a Hyperman worker detaches the socket and streams it on the loop; a psgi.streaming server uses the standard delayed-response writer; and blocking => 1 streams inside the handler over psgix.io (pinning one worker). Without any of them the request gets a 501.

Backpressure is bounded by write_buffer_limit as for websockets: a client that will not read is closed rather than allowed to buffer without limit.

THE ROUTE

sse '/events' => 'Live#feed';
sse '/events' => $target, { heartbeat => 30, retry => 3000 };

Options: heartbeat (seconds between keep-alive comments, default 15; 0 to turn it off), retry (the client reconnect delay in ms, sent once up front), write_buffer_limit, and blocking.

Reconnection

The browser reconnects automatically and sends the last id it saw as the Last-Event-ID header; read it to resume:

my $from = $c->req->header('last-event-id');

and stamp outgoing events with "id" so the client has something to send back.

THE STREAM

send($data)

One event. A reference is JSON-encoded; a multi-line string becomes multiple data: lines per the spec. Chainable.

event($name, $data)

A named event (an event: field the client dispatches by name). Chainable.

comment($text)

A :comment line - ignored by the client, useful as a keep-alive. Chainable.

id($id)

retry($ms)

Write an id: or retry: field. Send id just before the event it stamps. Chainable.

close

End the stream now. Chainable.

is_open

Whether the stream is still open - test it before pushing from a timer.

on(close => $cb)

$cb->($stream) once, when the stream ends: the client disconnecting, a close, or a write error. Chainable.

SEE ALSO

Punk, Punk::Future, Punk::WebSocket.

AUTHOR

LNATION <email@lnation.org>

LICENSE AND COPYRIGHT

This software is Copyright (c) 2026 by LNATION <email@lnation.org>.

This is free software, licensed under:

The Artistic License 2.0 (GPL Compatible)