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)