WebSockets and SSE
Both are route kinds, not add-ons: they sit in the same routing table, under the same scopes and guards as every other route, so a guard can reject a client with an ordinary HTTP response before any upgrade happens.
WebSockets
websocket '/chat' => 'WS::Chat#join';
websocket '/feed' => $target, { protocols => ['v1'] };
An upgrade request routes like a GET. Once the handshake is validated and answered, the handler is called with the context and the live connection:
package MyApp::Controller::WS::Chat;
sub join {
my ($c, $ws) = @_;
$ws->on(open => sub {
my ($ws) = @_;
Punk::WebSocket::Room->named('lobby')->join($ws);
});
$ws->on(message => sub {
my ($ws, $text) = @_;
Punk::WebSocket::Room->named('lobby')->broadcast($text);
});
$ws->on(close => sub {
my ($ws, $code, $reason) = @_;
Punk::WebSocket::Room->named('lobby')->leave($ws);
});
}
The handler wires the events it wants and returns; the connection then
lives on the server's event loop. Punk::WebSocket::Room gives
per-worker pub/sub rooms for broadcasting.
Options: protocols (an arrayref of acceptable subprotocols - a client
offering none of them is refused), max_message_size (default 16MB),
write_buffer_limit, and blocking.
WebSocket routes need Hyperman 0.11
or later, whose detach hands the socket to the application. On other
PSGI servers, blocking => 1 runs the connection inside the handler
over psgix.io - works anywhere, but pins one worker per connection.
Without either, to_app croaks rather than start with routes it cannot
serve.
Server-Sent Events
The lighter sibling: a one-way text/event-stream for a browser's
EventSource, with heartbeats and reconnect control.
sse '/events' => 'Live#feed';
sse '/events' => $target, { heartbeat => 30 };
package 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 }); # data: {"time":...}
$c->timer(1)->on_done($tick); # push once a second
};
$tick->();
$stream->on(close => sub { warn "client gone\n" });
}
send takes a string or a reference (JSON-encoded for you); events can
carry a name and id. Options: heartbeat (seconds, default 15),
retry (ms), write_buffer_limit, blocking.
Fully non-blocking on a Hyperman worker; portable to any
psgi.streaming server; blocking => 1 streams inside the handler over
psgix.io anywhere else.
Choosing between them
Reach for SSE when the server pushes and the client only listens - live
dashboards, progress, feeds. Reach for WebSockets when the client talks
back on the same connection - chat, games, collaborative editing. The
Chat example in Punk's example/ directory runs both from one app.