NAME

Net::HTTP2::nghttp2 - Perl XS bindings for nghttp2 HTTP/2 library

SYNOPSIS

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

# Check availability
die "nghttp2 not available" unless Net::HTTP2::nghttp2->available;

# See EXAMPLES below for complete server and client examples

DESCRIPTION

This module provides Perl bindings for the nghttp2 C library, enabling HTTP/2 protocol support in Perl applications.

nghttp2 is one of the most mature HTTP/2 implementations, used by curl, Apache, and Firefox. It implements RFC 9113 (HTTP/2) and RFC 7541 (HPACK).

CLASS METHODS

available

my $bool = Net::HTTP2::nghttp2->available;

Returns true if nghttp2 is available and properly linked.

CONSTANTS

Error Codes

NGHTTP2_ERR_WOULDBLOCK

Operation would block (non-fatal).

NGHTTP2_ERR_CALLBACK_FAILURE

Callback returned an error.

NGHTTP2_ERR_DEFERRED

Data production deferred (for flow control).

Frame Flags

NGHTTP2_FLAG_END_STREAM

End of stream flag.

NGHTTP2_FLAG_END_HEADERS

End of headers flag.

EXAMPLES

HTTP/2 Server (h2c - cleartext)

use IO::Socket::INET;
use Net::HTTP2::nghttp2;
use Net::HTTP2::nghttp2::Session;

my $server = IO::Socket::INET->new(
    LocalPort => 8080,
    Listen    => 128,
    ReuseAddr => 1,
) or die "Cannot create server: $!";

while (my $client = $server->accept) {
    my $session = Net::HTTP2::nghttp2::Session->new_server(
        callbacks => {
            on_begin_headers => sub {
                my ($stream_id) = @_;
                # New request starting
                return 0;
            },
            on_header => sub {
                my ($stream_id, $name, $value) = @_;
                # Received header: :method, :path, :scheme, etc.
                return 0;
            },
            on_frame_recv => sub {
                my ($frame) = @_;
                # HEADERS frame with END_STREAM = complete request
                if ($frame->{type} == 1 && ($frame->{flags} & 0x1)) {
                    $session->submit_response($frame->{stream_id},
                        status  => 200,
                        headers => [['content-type', 'text/plain']],
                        body    => "Hello, HTTP/2!\n",
                    );
                }
                return 0;
            },
            on_stream_close => sub {
                my ($stream_id, $error_code) = @_;
                return 0;
            },
        },
    );

    $session->send_connection_preface();

    # I/O loop
    while ($session->want_read || $session->want_write) {
        # Send pending data
        if (my $out = $session->mem_send) {
            $client->syswrite($out);
        }

        # Read incoming data
        my $buf;
        last unless $client->sysread($buf, 16384);
        $session->mem_recv($buf);
    }
    $client->close;
}

HTTP/2 Client (h2 - TLS)

use IO::Socket::SSL;
use Net::HTTP2::nghttp2;
use Net::HTTP2::nghttp2::Session;

# Connect with ALPN to negotiate HTTP/2
my $sock = IO::Socket::SSL->new(
    PeerHost           => 'nghttp2.org',
    PeerPort           => 443,
    SSL_alpn_protocols => ['h2'],
) or die "Connection failed: $!";

die "ALPN failed" unless $sock->alpn_selected eq 'h2';

my %response;
my $session = Net::HTTP2::nghttp2::Session->new_client(
    callbacks => {
        on_header => sub {
            my ($stream_id, $name, $value) = @_;
            $response{headers}{$name} = $value;
            return 0;
        },
        on_data_chunk_recv => sub {
            my ($stream_id, $data) = @_;
            $response{body} .= $data;
            return 0;
        },
        on_stream_close => sub {
            my ($stream_id, $error_code) = @_;
            $response{done} = 1;
            return 0;
        },
    },
);

$session->send_connection_preface();

# Submit GET request
my $stream_id = $session->submit_request(
    method    => 'GET',
    path      => '/',
    scheme    => 'https',
    authority => 'nghttp2.org',
    headers   => [['user-agent', 'perl-nghttp2/0.001']],
);

# I/O loop
while (!$response{done} && ($session->want_read || $session->want_write)) {
    if (my $out = $session->mem_send) {
        $sock->syswrite($out);
    }

    my $buf;
    last unless $sock->sysread($buf, 16384);
    $session->mem_recv($buf);
}

print "Status: $response{headers}{':status'}\n";
print "Body: " . length($response{body}) . " bytes\n";

Streaming Response

# In on_frame_recv callback, use a data provider for large responses:
$session->submit_response($stream_id,
    status  => 200,
    headers => [['content-type', 'application/octet-stream']],
    body    => sub {
        my ($stream_id, $max_length) = @_;
        my $chunk = get_next_chunk($max_length);
        my $eof = is_last_chunk() ? 1 : 0;
        return ($chunk, $eof);
    },
);

# Return undef to defer, then call resume_stream() when data ready:
body => sub {
    my ($stream_id, $max_length) = @_;
    return undef if !data_available();  # Defers
    return (get_data(), $eof);
},

# Later, when data becomes available:
$session->resume_stream($stream_id);

CONFORMANCE TESTING

This module has been tested against h2spec (https://github.com/summerwind/h2spec), the HTTP/2 conformance testing tool.

h2spec Results

146 tests, 137 passed, 1 skipped, 8 failed (94% pass rate)

Passing Test Categories

  • Starting HTTP/2 - Connection preface handling

  • Streams and Multiplexing - Stream state management

  • Frame Definitions - DATA, HEADERS, PRIORITY, RST_STREAM, SETTINGS, PING, GOAWAY, WINDOW_UPDATE, CONTINUATION

  • HTTP Message Exchanges - GET, HEAD, POST requests with trailers

  • HPACK - All header compression variants (indexed, literal, Huffman)

  • Server Push - PUSH_PROMISE handling

Known Limitations

The 8 failing tests are edge cases where nghttp2 intentionally chooses lenient behavior over strict RFC compliance for better interoperability:

  • Invalid connection preface - nghttp2 sends SETTINGS before validating

  • DATA/HEADERS on closed streams - Silently ignored rather than erroring

  • Out-of-order stream identifiers - Accepted (lenient parsing)

  • PRIORITY self-dependency - Ignored rather than treated as error

  • PRIORITY on stream 0 - Silently ignored

This lenient behavior is intentional in nghttp2 and matches the behavior of production HTTP/2 implementations like curl, Apache, and nginx.

SEE ALSO

Alien::nghttp2 - Alien module for automatic nghttp2 installation

https://nghttp2.org/ - nghttp2 project homepage

https://datatracker.ietf.org/doc/html/rfc9113 - HTTP/2 RFC

https://github.com/summerwind/h2spec - HTTP/2 conformance testing tool

AUTHOR

Your Name <your@email.com>

LICENSE

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.