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.