PAGI-Server

 view release on metacpan or  search on metacpan

lib/PAGI/Server/Compliance.pod  view on Meta::CPAN


=item * Flow control (per-stream and connection-level)

=item * GOAWAY handling (graceful session shutdown)

=item * Stream state validation (END_STREAM tracking, RST_STREAM on violations)

=item * Extended CONNECT for WebSocket over HTTP/2 (RFC 8441)

=item * Server SETTINGS: configurable max_concurrent_streams, initial_window_size,
max_frame_size, max_header_list_size

=item * A request HEADERS block exceeding max_header_list_size gets a real
431 response (RFC 9113 section 10.5.1) instead of a bare RST_STREAM,
matching HTTP/1.1

=item * HEAD request body suppression, matching HTTP/1.1: DATA frames are
withheld and file/fh bodies are never opened

=item * File and filehandle body streaming through the per-stream send queue,
under the same per-stream backpressure watermark as chunked bodies

=item * C<http.fullflush> on HTTP and SSE streams

=back

Filehandle (C<fh>) response bodies are read B<synchronously>, in fixed-size
chunks inside the connection's own send loop, on both HTTP/1.1 and HTTP/2 --
the handle is application-owned and cannot be handed to the async worker
pool across a fork boundary. This differs from C<file> (path) bodies opened
by the server, which use the async worker pool (L<PAGI::Server::AsyncFile>)
for files larger than C<sync_file_threshold>; a C<fh> body bypasses that
pool regardless of size.

=head2 Transport Flow Control (pagi.transport)

PAGI exposes outbound flow-control introspection to applications through the
C<pagi.transport> scope key (see L<PAGI::Spec::Www/"Transport Flow Control">):
C<buffered_amount>, the high/low watermarks, and the
C<on_high_water>/C<on_drain> backpressure callbacks. Over HTTP/2 the handle
measures the B<per-stream> send backlog, so each multiplexed stream is bounded
independently.

PAGI::Server provides C<pagi.transport> for every streaming scope type on both
transports: C<http> (streaming responses), C<sse>, and C<websocket>, on both
HTTP/1.1 and HTTP/2. The HTTP/2 WebSocket send path frames application
messages, protocol replies (pong, the close-frame echo), and its own keepalive
ping through the same per-stream send queue and pull-based data-provider model
that HTTP/2 streaming and SSE use. From the application's perspective the
transport is invisible: a WebSocket app sees the same C<pagi.transport>
surface and semantics -- C<buffered_amount>, the high/low watermarks,
C<on_high_water>/C<on_drain> -- whether the connection is HTTP/1.1 or
HTTP/2.

C<on_drain> fires only for a genuine drain -- the buffer actually falling
back below the low mark -- on both transports. Tearing a connection down
while the buffer is still above the high mark (client disconnect, timeout,
server shutdown, ...) does not fire C<on_drain>: the connection is going
away, not draining. A producer parked on the blocking backpressure path
(a C<$send> awaiting the buffer to drain) still resumes on teardown, so no
coroutine is left hanging; only the app-facing hysteresis callback is
withheld.

=head2 WebSocket over HTTP/2 (RFC 8441)

PAGI::Server accepts a WebSocket upgrade over HTTP/2 through RFC 8441
Extended CONNECT (C<:protocol =E<gt> 'websocket'>). Each accepted stream carries
its own C<websocket.receive>/C<websocket.disconnect> event stream,
multiplexed alongside every other stream on the connection.

=head3 Framing Enforcement

RFC 8441 requires WebSocket-over-HTTP/2 framing to be "identical to
HTTP/1.1" (section 4), so the same three RFC 6455 framing checks the
HTTP/1.1 WebSocket compliance section above performs also apply here, per
stream: nonzero RSV1-3 bits close with
C<1002> (section 5.2; C<Protocol::WebSocket::Frame> exposes C<rsv> but
does not itself validate it), reserved/unknown opcodes C<3-7> and
C<11-15> close with C<1002> (section 5.2), and a control frame
(close/ping/pong) payload over 125 bytes closes with C<1002> (section
5.5). Each violation follows the same server-initiated-protocol-close path
described below: one Close frame on the wire, one C<websocket.disconnect>
(code C<1002>, reason C<protocol_error>) delivered to the application.

=head3 Keepalive

C<websocket.keepalive> is supported per stream. An HTTP/2 connection
multiplexes many WebSocket streams, so keepalive state and its timers live
on the individual stream rather than the connection -- unlike HTTP/1.1,
where one WebSocket occupies the whole connection and keepalive is
connection-wide. A ping is delivered as an RFC 6455 ping frame carried in
an HTTP/2 C<DATA> frame on that stream; the peer's pong (opcode 10) clears
the stream's wait flag. Each C<websocket.keepalive> event for a stream
supersedes any keepalive already running on it, and C<interval =E<gt> 0>
stops it. When C<timeout> is given and no pong arrives
within it, only that stream closes -- code C<1006>, reason
C<keepalive_timeout> -- other streams multiplexed on the same connection
are unaffected. That C<1006> is the code the application sees; it never
reaches the wire, because RFC 6455 section 7.4.1 forbids C<1006> as the
status code of a Close frame. A keepalive timeout sends no Close frame at
all: it resets the stream (C<RST_STREAM>, error code C<CANCEL>), the
HTTP/2 analogue of HTTP/1.1 dropping the transport. An omitted C<timeout>
means the server never checks for a missing pong.

=head3 Disconnect

Each HTTP/2 WebSocket stream enqueues exactly one C<websocket.disconnect>
event onto its scope's receive queue, whichever of the conditions below
closes it first: the closure paths all funnel through one deduplicating
enqueue, so no closure can queue a second disconnect and no two closure
paths can race to queue conflicting ones. C<code> and C<reason> pair up the
same way as
L<the WebSocket "Disconnect - receive event" section of PAGI::Spec::Www|PAGI::Spec::Www>
describes for the reference server:

=over 4

=item * B<The peer sent a Close frame.> C<code> is the peer's own close
code (C<1005> if the frame carried none) and C<reason> is the peer's own
reason text, which is commonly empty. The server does not substitute a
token.



( run in 2.306 seconds using v1.01-cache-2.11-cpan-364913b4093 )