NAME

PAGI::Server::Protocol::HTTP2 - HTTP/2 protocol handler using nghttp2

SYNOPSIS

use PAGI::Server::Protocol::HTTP2;

my $proto = PAGI::Server::Protocol::HTTP2->new;

if ($proto->available) {
    my $session = $proto->create_session(
        on_request => sub { ... },
        on_body    => sub { ... },
        on_close   => sub { ... },
    );
}

DESCRIPTION

PAGI::Server::Protocol::HTTP2 provides HTTP/2 support for PAGI::Server using the nghttp2 C library via Net::HTTP2::nghttp2.

Unlike HTTP/1.1, HTTP/2 uses binary framing, multiplexed streams on a single connection, HPACK header compression, and per-stream flow control.

This module bridges nghttp2's callback-based API to PAGI's event model.

available

if (PAGI::Server::Protocol::HTTP2->available) { ... }

Returns true if HTTP/2 support is usable — that is, if Net::HTTP2::nghttp2 (at least version MIN_NGHTTP2_VERSION) and its Session class loaded and the underlying nghttp2 library reports itself available. Returns false otherwise. Checked once at module load.

detect_preface

if (PAGI::Server::Protocol::HTTP2->detect_preface($bytes)) { ... }

Returns true if $bytes starts with the HTTP/2 client connection preface. Used for h2c (cleartext HTTP/2) detection.

new

my $proto = PAGI::Server::Protocol::HTTP2->new(
    max_concurrent_streams  => 100,                      # Default
    initial_window_size     => 65535,                    # Default
    max_frame_size          => 16384,                    # Default
    enable_push             => 0,                        # Default (disabled)
    enable_connect_protocol => 1,                        # Default (enabled, RFC 8441)
    max_header_list_size    => 65536,                    # Default (64KB)
    h2_rst_rate_limit       => { burst => 1000, rate => 33 }, # Default (Rapid Reset defense)
);

Creates a new HTTP/2 protocol handler with the specified settings.

create_session

my $session = $proto->create_session(
    on_request => sub { ($stream_id, $pseudo, $headers, $has_body) = @_ },
    on_body    => sub { ($stream_id, $data, $eof) = @_ },
    on_close   => sub { ($stream_id, $error_code) = @_ },
    on_header_overflow => sub { ($stream_id) = @_ },
);

Creates a new HTTP/2 session for a connection. Returns a PAGI::Server::Protocol::HTTP2::Session wrapper.

on_header_overflow is optional. It fires instead of on_request when a request's HEADERS block exceeds max_header_list_size (RFC 9113 section 10.5.1): the caller is expected to submit a 431 response on the given stream id (no request state was ever dispatched, so there is nothing to clean up on the caller's side). If omitted, an oversized request is simply never dispatched and no response is sent for it.

feed

my $consumed = $session->feed($data);

Feed incoming data to the HTTP/2 session. Returns bytes consumed.

extract

my $data = $session->extract;

Extract outgoing data from the session. Returns bytes to send.

want_read

if ($session->want_read) { ... }

Check if session wants to read.

want_write

if ($session->want_write) { ... }

Check if session has data to write.

submit_response

$session->submit_response($stream_id,
    status  => 200,
    headers => [['content-type', 'text/html']],
    body    => $body,
);

Submit a response on a stream. body can be a string (sent as single response) or a coderef for streaming.

submit_response_streaming

$session->submit_response_streaming($stream_id,
    status        => 200,
    headers       => [['content-type', 'text/event-stream']],
    data_callback => sub {
        my ($stream_id, $max_len) = @_;
        return ($chunk, $is_eof);
    },
);

Submit a streaming response with a data provider callback.

resume_stream

$session->resume_stream($stream_id);

Resume a deferred stream after data becomes available.

submit_trailer

$session->submit_trailer($stream_id, headers => [['x-checksum', 'abc']]);

Submit the trailing HEADERS block for a response (design section 8.3): queues NGHTTP2_FLAG_END_STREAM on a HEADERS frame after any already-queued DATA, completing a response whose data provider reserved END_STREAM for the trailers (a three-value ($chunk, $eof, $no_end_stream) data-callback return). headers defaults to [] for a declared-but-empty trailer block. Throws (via the underlying Net::HTTP2::nghttp2::Session) on a malformed tuple or a pseudo-header name. An unknown or already-gone stream id does not throw -- it returns a falsy nghttp2 status; stream_id == 0 is the one id nghttp2 rejects with an immediate croak (invalid argument).

submit_data

$session->submit_data($stream_id, $data, $eof);

Push data directly onto a stream. Used for WebSocket frame delivery over HTTP/2 where frames are sent as DATA payloads.

terminate

$session->terminate($error_code);

Terminate the session with GOAWAY.

submit_rst_stream

$session->submit_rst_stream($stream_id, $error_code);

Reset a single stream with RST_STREAM, leaving the rest of the session untouched. $error_code is an RFC 9113 section 7 error code (for example 2, INTERNAL_ERROR).

Used by the server's HTTP/2 dispatch to abort a stream whose response was left started but incomplete -- the application returned early or threw after http.response.start, so there is no honest way to finish the body.

HTTP/2 vs HTTP/1.1

Key differences that affect PAGI integration:

  • Multiplexing - Multiple concurrent requests on one TCP connection

  • Binary Framing - nghttp2 handles all framing; PAGI feeds/extracts bytes

  • Header Compression - HPACK is built into nghttp2

  • Flow Control - Per-stream and connection-level, via streaming callbacks

SEE ALSO

Net::HTTP2::nghttp2, PAGI::Server::Protocol::HTTP1