Net-HTTP2-nghttp2

 view release on metacpan or  search on metacpan

lib/Net/HTTP2/nghttp2.pm  view on Meta::CPAN

            },
            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";

=head2 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);

=head2 Streaming Request

Client-side requests also support streaming body callbacks, using the same
callback signature as streaming responses. This is essential for bidirectional
protocols like WebSocket over HTTP/2 (RFC 8441), where the client stream must
remain open for ongoing data exchange.

    my @send_queue;
    my $eof = 0;

    my $stream_id = $session->submit_request(
        method    => 'POST',
        path      => '/upload',
        scheme    => 'https',
        authority => 'example.com',
        body      => sub {
            my ($stream_id, $max_length) = @_;
            if (@send_queue) {
                my $chunk = shift @send_queue;
                return ($chunk, $eof);
            }
            return undef;  # Defer until data available
        },
    );

    # Later, queue data and resume:
    push @send_queue, $data;
    $session->resume_stream($stream_id);

=head2 WebSocket over HTTP/2 (RFC 8441)

RFC 8441 defines how to bootstrap WebSocket connections over HTTP/2 using the
extended CONNECT method. The server advertises support via
C<SETTINGS_ENABLE_CONNECT_PROTOCOL>, and the client sends an extended CONNECT
request with a C<:protocol> pseudo-header set to C<websocket>.

B<Server side> - enable the setting and detect extended CONNECT:

    $session->send_connection_preface(
        enable_connect_protocol => 1,
    );

    # In on_header callback, look for :protocol pseudo-header:
    on_header => sub {
        my ($stream_id, $name, $value) = @_;
        if ($name eq ':method' && $value eq 'CONNECT') {
            # Possible extended CONNECT
        }
        if ($name eq ':protocol' && $value eq 'websocket') {
            # This is a WebSocket upgrade request
        }
        return 0;
    },

    # Accept with 200 (not 101) and a streaming body for server-to-client:
    $session->submit_response($stream_id,
        status  => 200,
        headers => [['sec-websocket-protocol', $subprotocol]],
        body    => sub {
            my ($stream_id, $max_length) = @_;
            # Return WebSocket frames to send to client
            return undef;  # Defer until data ready
        },
    );

B<Client side> - send extended CONNECT with streaming body:

    my $stream_id = $session->submit_request(
        method    => 'CONNECT',
        path      => '/chat',
        scheme    => 'https',
        authority => 'example.com',
        headers   => [[':protocol', 'websocket']],
        body      => sub {
            my ($stream_id, $max_length) = @_;
            # Return WebSocket frames to send to server
            return undef;  # Defer until data ready
        },
    );

WebSocket frames (RFC 6455) are carried inside HTTP/2 DATA frames on the
stream. Both directions remain open until one side sends a DATA frame with
END_STREAM (by returning C<($data, 1)> from the callback).

=head1 CONFORMANCE TESTING

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

=head2 h2spec Results

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

=head2 Passing Test Categories

=over 4

=item * Starting HTTP/2 - Connection preface handling

=item * Streams and Multiplexing - Stream state management

=item * Frame Definitions - DATA, HEADERS, PRIORITY, RST_STREAM, SETTINGS, PING, GOAWAY, WINDOW_UPDATE, CONTINUATION

=item * HTTP Message Exchanges - GET, HEAD, POST requests with trailers

=item * HPACK - All header compression variants (indexed, literal, Huffman)

=item * Server Push - PUSH_PROMISE handling

=back

=head2 Known Limitations

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

=over 4

=item * Invalid connection preface - nghttp2 sends SETTINGS before validating

=item * DATA/HEADERS on closed streams - Silently ignored rather than erroring

=item * Out-of-order stream identifiers - Accepted (lenient parsing)

=item * PRIORITY self-dependency - Ignored rather than treated as error



( run in 1.211 second using v1.01-cache-2.11-cpan-140bd7fdf52 )