NAME

Net::HTTP2::nghttp2::Session - HTTP/2 session management

SYNOPSIS

use Net::HTTP2::nghttp2::Session;

my $session = Net::HTTP2::nghttp2::Session->new_server(
    callbacks => {
        on_begin_headers => sub {
            my ($session, $stream_id) = @_;
            # New stream started
        },
        on_header => sub {
            my ($session, $stream_id, $name, $value, $flags) = @_;
            # Header received
        },
        on_frame_recv => sub {
            my ($session, $frame) = @_;
            # Frame received
        },
        on_stream_close => sub {
            my ($session, $stream_id, $error_code) = @_;
            # Stream closed
        },
        on_data_chunk_recv => sub {
            my ($session, $stream_id, $data, $flags) = @_;
            # Body data received
        },
    },
);

# Send connection preface
$session->send_connection_preface(
    max_concurrent_streams => 100,
);

# Process incoming data
$session->mem_recv($incoming_bytes);

# Get outgoing data to send
my $outgoing = $session->mem_send();

# Submit a response
$session->submit_response($stream_id,
    status  => 200,
    headers => [
        ['content-type', 'text/html'],
    ],
    body => '<html>...</html>',
);

METHODS

new_server

my $session = Net::HTTP2::nghttp2::Session->new_server(%args);

Create a new server-side HTTP/2 session.

Arguments:

callbacks

Hashref of callback handlers. Required callbacks: on_begin_headers, on_header, on_frame_recv. Optional: on_data_chunk_recv, on_stream_close, on_frame_send, on_frame_not_send, on_invalid_frame_recv, on_error.

user_data

Optional scalar passed to callbacks.

settings

Optional hashref of initial HTTP/2 settings.

stream_reset_burst / stream_reset_rate

Configure the incoming-RST_STREAM rate limit (nghttp2's HTTP/2 Rapid Reset, CVE-2023-44487, mitigation). Both must be given together. They map to nghttp2_option_set_stream_reset_rate_limit(option, burst, rate): a token bucket of burst tokens refilling at rate tokens/second, one token per incoming RST_STREAM. When the bucket is empty nghttp2 sends GOAWAY and tears down the connection. Omit both to use nghttp2's own defaults (burst 1000, rate 33). Requires nghttp2 >= 1.57.

new_client

my $session = Net::HTTP2::nghttp2::Session->new_client(%args);

Create a new client-side HTTP/2 session.

Arguments:

callbacks

Hashref of callback handlers. Recommended: on_header, on_data_chunk_recv, on_stream_close. The optional on_frame_send, on_frame_not_send, on_invalid_frame_recv and on_error callbacks are accepted here too.

user_data

Optional scalar passed to callbacks.

send_connection_preface

$session->send_connection_preface(%settings);

Send HTTP/2 connection preface (SETTINGS frame). Default settings: max_concurrent_streams => 100, initial_window_size => 65535.

Additional settings:

enable_connect_protocol

Set to 1 to advertise RFC 8441 extended CONNECT support (SETTINGS_ENABLE_CONNECT_PROTOCOL). Required for WebSocket over HTTP/2.

mem_recv

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

Feed incoming data to the session. Returns number of bytes consumed. Triggers registered callbacks as frames are parsed.

mem_send

my $data = $session->mem_send();

Get outgoing data from the session. Returns bytes to send to peer (empty string if nothing pending).

submit_request

my $stream_id = $session->submit_request(%args);

Submit an HTTP/2 request (client-side). Returns the stream ID.

Arguments:

method

HTTP method. Default: 'GET'.

path

Request path. Default: '/'.

scheme

URL scheme. Default: 'https'.

authority

Host authority (e.g. 'example.com').

headers

Arrayref of [$name, $value] pairs for additional headers (including pseudo-headers like :protocol for RFC 8441 extended CONNECT).

body

Request body. Can be:

undef (or omitted)

No body. HEADERS frame sent with END_STREAM.

String

Static body. Sent as DATA frame(s) with END_STREAM after the last frame.

CODE ref

Streaming callback for bidirectional streams. The callback receives ($stream_id, $max_length) and must return one of:

($data, $eof_flag)

Send data. A true EOF ends the stream, preserving pre-0.009 behavior.

($data, $eof_flag, $no_end_stream)

Send data. When both flags are true, content production is complete but DATA does not carry END_STREAM, allowing submit_trailer() to queue the terminal HEADERS block. The third value has no effect unless EOF is true.

undef or an empty list

Defer production until the stream is resumed or submit_data() is called.

This is required for protocols that keep the stream open for bidirectional exchange, such as WebSocket over HTTP/2 (RFC 8441 extended CONNECT).

submit_response

$session->submit_response($stream_id, %args);

Submit an HTTP/2 response on the given stream.

Arguments:

status

HTTP status code. Default: 200.

headers

Arrayref of [$name, $value] pairs.

body

Response body. Same types as submit_request: undef (no body), string (static body), or CODE ref (streaming callback).

undef (or omitted)

No body. HEADERS frame sent with END_STREAM.

String

Static body. Sent as DATA frame(s) with END_STREAM after the last frame.

CODE ref

Streaming callback. It receives ($stream_id, $max_length), or ($stream_id, $max_length, $user_data) when callback_data is defined, and must return one of:

($data, $eof_flag)

Send data. A true EOF ends the stream, preserving pre-0.009 behavior.

($data, $eof_flag, $no_end_stream)

Send data. When both flags are true, content production is complete but DATA does not carry END_STREAM, allowing submit_trailer() to queue the terminal HEADERS block. The third value has no effect unless EOF is true.

undef or an empty list

Defer production until the stream is resumed or submit_data() is called.

data_callback

Alternative to passing a CODE ref as body. Callback with the same streaming signature.

callback_data

Optional user data passed as third argument to the streaming callback.

A stream carries at most one body provider. Submitting a second response with a body on a stream that already has one croaks with stream N already has a data provider, leaving the response already in flight untouched.

submit_trailer

$session->submit_trailer(
    $stream_id,
    headers => [
        ['x-checksum', 'abc'],
        ['set-cookie', 'a=1'],
        ['set-cookie', 'b=2'],
    ],
);

Queue a trailing HEADERS block that ends the stream. headers defaults to an empty array reference; order and duplicate names are preserved. Trailer names must be ordinary field names, not pseudo-header names beginning with :.

When trailers follow, the data provider must ultimately report EOF together with NO_END_STREAM, using ($data, 1, 1) or submit_data($stream_id, $data, 1, 1), so the final DATA does not consume END_STREAM. submit_trailer may be called inside the data callback or after that callback returns. Callers that do not send trailers should use the legacy two-value ($data, $eof_flag) callback or three-argument submit_data($stream_id, $data, $eof) form, where a true EOF retains the normal DATA END_STREAM behavior. An empty headers list queues an empty terminal HEADERS block.

A zero return means nghttp2 accepted the trailer block into its outbound queue. It does not mean the peer has received it. Invalid Perl input and immediate nghttp2 submission errors throw exceptions.

submit_data

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

Push data directly onto an existing stream. The stream must already have a data provider (established by submit_request or submit_response with a CODE ref or data_callback). This replaces the streaming callback with a one-shot static body, then resumes the stream.

Arguments:

$stream_id

The stream to send data on.

$data

The data to send. Can be undef for an empty DATA frame.

$eof

If true, the DATA frame will include END_STREAM, closing the stream.

$no_end_stream

Optional and false by default. When true together with a true $eof, content production is complete but DATA does not include END_STREAM, allowing submit_trailer() to queue the terminal HEADERS block. It has no effect unless $eof is true. The three-argument form retains its existing behavior: a true $eof ends the stream on DATA.

This is useful when you have data available outside the streaming callback context and want to push it directly, such as forwarding WebSocket frames received from another source.

resume_stream

$session->resume_stream($stream_id);

Resume data production for a deferred stream. Call this after a streaming body callback has returned undef and new data is available. Works for both request and response streams.

terminate_session

$session->terminate_session($error_code);

Send a GOAWAY frame and terminate the session. The $error_code should be an HTTP/2 wire error code (0 for NGHTTP2_NO_ERROR).

submit_rst_stream

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

Send a RST_STREAM frame to abnormally terminate a stream. The $error_code should be an HTTP/2 error code (e.g. 0 for NO_ERROR, 8 for CANCEL).

submit_goaway

$session->submit_goaway(
    last_stream_id => $stream_id,
    error_code     => NGHTTP2_NO_ERROR,
    opaque_data    => 'shutting down',
);

Queue a GOAWAY frame announcing a graceful shutdown. Streams numbered above last_stream_id are abandoned; the peer may still finish the ones at or below it. The caller still calls mem_send to flush the frame.

Arguments:

last_stream_id

Required. The highest peer-initiated stream this session will still process. It must be a stream the peer could have opened: odd or zero for a server session, even or zero for a client session. HTTP/2 forbids raising this value once announced, so nghttp2 sends the lower of this value and any previously sent one. Must be an integer in 0 .. 0x7FFFFFFF; anything outside that range is refused before it reaches nghttp2.

error_code

An HTTP/2 wire error code. Defaults to NGHTTP2_NO_ERROR.

opaque_data

Optional debug data carried with the frame, copied at submission time. Must be a byte string (octets), not a decoded character string; a wide-character string croaks.

A zero return means nghttp2 accepted the frame into its outbound queue. An immediate nghttp2 submission error, such as an invalid last_stream_id, throws an exception.

get_stream_remote_close

my $closed = $session->get_stream_remote_close($stream_id);

Returns 1 when the remote peer has half closed the stream, 0 when it may still send on it, and undef when no such stream exists. On a server session this answers whether the request half is finished.

get_stream_local_close

my $closed = $session->get_stream_local_close($stream_id);

Returns 1 when this session has half closed the stream, 0 when it may still send on it, and undef when no such stream exists. On a server session this answers whether the response half is finished.

A stream is dropped once both halves close, so both queries answer undef for a fully closed stream rather than 1.

submit_ping

$session->submit_ping($ack, $opaque_data);

Send a PING frame. Set $ack to 1 for a PING ACK response, 0 for an unsolicited PING. $opaque_data must be exactly 8 bytes, or undef for default.

submit_window_update

$session->submit_window_update($stream_id, $window_size_increment);

Send a WINDOW_UPDATE frame to increase the flow control window. Use $stream_id = 0 for connection-level flow control, or a specific stream ID for stream-level.

get_stream_user_data

my $data = $session->get_stream_user_data($stream_id);

Retrieve user data associated with a stream. Returns undef if no data is set.

set_stream_user_data

$session->set_stream_user_data($stream_id, $data);

Associate arbitrary user data with a stream. Useful for storing per-stream application state.

is_stream_deferred

my $bool = $session->is_stream_deferred($stream_id);

Returns true if the stream's data provider has been deferred (i.e. the streaming callback returned undef). The stream can be resumed with resume_stream().

want_read

my $bool = $session->want_read();

Returns true if the session wants to read more data.

want_write

my $bool = $session->want_write();

Returns true if the session has data to write.

resume_data

$session->resume_data($stream_id);

Low-level resume for deferred data production. Prefer resume_stream() which also clears the internal deferred flag.

CALLBACKS

All callbacks receive positional arguments and should return 0 on success.

on_begin_headers

sub { my ($stream_id, $frame_type, $flags) = @_; return 0; }

Called when a new headers block begins (new stream or trailers).

on_header

sub { my ($stream_id, $name, $value, $flags) = @_; return 0; }

Called for each header. Pseudo-headers (:method, :path, :scheme, :authority, :status, :protocol) are delivered before regular headers.

on_frame_recv

sub { my ($frame_hashref) = @_; return 0; }

Called when a complete frame is received. The hashref contains: type, flags, stream_id, length. A HEADERS frame additionally contains headers_category, one of:

NGHTTP2_HCAT_REQUEST

Initial request headers.

NGHTTP2_HCAT_RESPONSE

Initial response headers.

NGHTTP2_HCAT_PUSH_RESPONSE

Pushed response headers.

NGHTTP2_HCAT_HEADERS

A later ordinary HEADERS block on an open stream.

NGHTTP2_HCAT_HEADERS is not a universal is_trailer boolean: informational responses and message direction/state remain relevant when interpreting a later headers block.

on_data_chunk_recv

sub { my ($stream_id, $data, $flags) = @_; return 0; }

Called when body data is received on a stream.

on_stream_close

sub { my ($stream_id, $error_code) = @_; return 0; }

Called when a stream is closed.

on_frame_send

sub { my ($frame_hashref) = @_; return 0; }

Called when a frame has been serialized, with the same hashref shape as on_frame_recv. Optional.

"Sent" here means written into the buffer the next mem_send returns, not acknowledged or even transmitted by the peer. It is the point at which a frame carrying END_STREAM has irrevocably claimed its place in the output, which is what lets a caller reset the remaining half of a stream without racing flow control.

on_frame_not_send

sub { my ($frame_hashref, $lib_error_code) = @_; return 0; }

Called when a queued non-DATA frame was discarded instead of serialized, most often because the stream was reset while the frame was still waiting. The $lib_error_code is a negative NGHTTP2_ERR_* value, typically NGHTTP2_ERR_STREAM_CLOSING. Optional.

nghttp2 reports only non-DATA frames here. A pending DATA frame dropped by a reset produces no call, so silence is not proof that a body was delivered.

A response submitted for a stream that was already closed is accepted by nghttp2 and discarded here with NGHTTP2_ERR_STREAM_CLOSED. No on_stream_close follows, because the stream closed before the response was submitted, so the body provider is released at this point instead. As with any release during a session call, the callback_data is destroyed when the mem_send or mem_recv driving the flush returns. A discarded frame whose stream is still open leaves that stream's provider alone.

on_invalid_frame_recv

sub { my ($frame_hashref, $lib_error_code) = @_; return 0; }

Called when nghttp2 rejects a non-DATA frame from the peer, before it submits the RST_STREAM or GOAWAY the rejection calls for. The $lib_error_code is a negative NGHTTP2_ERR_* value describing the violation. For a rejected HEADERS or PUSH_PROMISE frame nghttp2 supplies no header fields. Optional.

on_error

sub { my ($lib_error_code, $message) = @_; return 0; }

Called when nghttp2 has a human-readable diagnostic to offer, such as a header field HTTP/2 does not permit. Intended for logging only: nghttp2 documents the wording as free to change between library versions, so match on $lib_error_code rather than on $message. Optional.

Reentrancy

A callback may queue further frames with the submit_* methods, including from inside on_frame_send. nghttp2 serializes them in order during the same flush. That includes resetting the stream the current frame belongs to: submit_rst_stream from inside a callback is supported, and the response body provider for a stream closed that way is held until the flush returns, so the reset is safe even while the provider still has data pending. Such a provider produces no further data, and its Perl callback is not invoked again.

mem_send and mem_recv drive the flush, so calling either from inside a callback croaks with mem_send called from inside a session callback or mem_recv called from inside a session callback. Catching that exception leaves the session usable; the flush already under way continues normally.

A provider released during a session call that ends by a Perl exception is reclaimed by the next session call or by DESTROY, not immediately.

Destroying the session ends it for every method. Releasing the providers it still owns runs their callback_data destructors, and a destructor that calls back into the session it is being torn down with croaks with Net::HTTP2::nghttp2::Session: session has been destroyed rather than reaching a session nghttp2 has already deleted.