NAME
PAGI::Server::Connection - Per-connection state machine
SYNOPSIS
# Internal use by PAGI::Server
my $conn = PAGI::Server::Connection->new(
stream => $stream,
app => $app,
protocol => $protocol,
server => $server,
extensions => {},
);
$conn->start;
DESCRIPTION
PAGI::Server::Connection manages the state machine for a single client connection. It handles:
Request parsing via Protocol::HTTP1
Scope creation for the application
Event queue management for $receive and $send
Protocol upgrades (WebSocket, SSE)
SSE over HTTP/1.1 and HTTP/2
Connection lifecycle and cleanup
SSE OVER HTTP/2
SSE events (sse.start, sse.send, sse.comment, sse.keepalive) work transparently over both HTTP/1.1 and HTTP/2. Applications do not need to change their SSE handling code based on protocol version.
How It Works
When a request arrives with Accept: text/event-stream, the connection detects it as SSE regardless of HTTP version. Over HTTP/1.1, SSE data is sent using chunked Transfer-Encoding. Over HTTP/2, SSE data is sent as DATA frames via the submit_response_streaming/data_callback mechanism. This difference is transparent to the application.
The http_version field in the scope hash will be '2' for HTTP/2 connections, allowing applications to distinguish if needed.
SSE Idle Timeout over HTTP/2
The sse_idle_timeout setting applies at the connection level, not per-stream. Over HTTP/1.1, this is a non-issue since each SSE stream occupies its own TCP connection. Over HTTP/2, where multiple streams share a single connection, an idle SSE stream timeout will close the entire connection, terminating all active streams.
Trade-offs
Pros:
Simple, consistent behavior across protocols
Matches the approach used by Go (net/http2), Rust (hyper/Axum), Java (Netty/Reactor Netty/Vert.x), Python (Hypercorn), and gRPC
No additional complexity in stream lifecycle management
Cons:
Closing the connection affects all multiplexed HTTP/2 streams, not just the idle SSE stream
Clients multiplexing SSE + REST on one HTTP/2 connection may see unexpected disconnects on their REST requests
Recommendation: Use SSE keepalive comments (sse.keepalive event) with an interval shorter than sse_idle_timeout to prevent the timer from firing. This is the industry-standard approach used across all major frameworks. For production deployments behind reverse proxies (Envoy, Nginx, HAProxy), align your keepalive interval with the proxy's stream idle timeout.
Note: Per-stream idle timeout (using HTTP/2 RST_STREAM to close only the idle SSE stream) is a future enhancement. Only Node.js (http2stream.setTimeout) and Envoy (stream_idle_timeout) implement this among mainstream servers.
SEE ALSO
PAGI::Server, PAGI::Server::Protocol::HTTP1
AUTHOR
John Napiorkowski <jjnapiork@cpan.org>
LICENSE
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.