Unblock::HTTP3

CPAN version CPANTS Kwalitee CI HTTP/3 interop License Perl

Unblock::HTTP3 is a non-blocking HTTP/3 protocol engine for Perl.

It handles HTTP/3 framing, QPACK, request streams, trailers, informational responses, Extended CONNECT, HTTP Datagrams, Capsules, priorities, graceful shutdown, and replay-aware 0-RTT state.

It does not own UDP sockets, TLS, timers, or an event loop. Net::QUIC owns the QUIC transport.

application or HTTP library
        |
  Uniform::HTTP messages
        |
   Unblock::HTTP3
        |
      Net::QUIC
        |
        UDP

Installation

From CPAN:

cpanm Unblock::HTTP3

Unblock::HTTP3 0.10 requires:

Perl            5.20+
Alien::nghttp3  0.01+
Net::QUIC       0.04+
Uniform::HTTP   0.06+

Version 0.10 intentionally breaks the earlier 0.03 Perl API. Client and Server now use new(), final responses use respond(), and the old Connection->client(), Connection->server(), and send_response() entry points are removed. There are no compatibility aliases.

Start here

The public API is built around three objects:

The common application vocabulary intentionally matches the other Unblock HTTP engines:

Client->new
Server->new
request()
respond()
write()
end()
send_informational()

HTTP/3 protocol concepts keep their HTTP/3 names.

Exact canonical Uniform::HTTP::Request and Uniform::HTTP::Response objects use the Uniform::HTTP native fast path. Uniform-compatible adapters and subclasses use the portable Perl message contract.

Client

use Uniform::HTTP::Request;
use Unblock::HTTP3::Client;

my $client = Unblock::HTTP3::Client->new(
    quic => $quic,
);

$client->start;

my $transaction = $client->request(
    Uniform::HTTP::Request->new(
        method    => 'GET',
        target    => '/',
        scheme    => 'https',
        authority => 'example.com',
    ),

    on_response => sub {
        my ($transaction, $response) = @_;
        print $response->status, "\n";
    },

    on_body => sub {
        my ($transaction, $response, $bytes) = @_;
        process_bytes($bytes);
    },

    on_complete => sub {
        my ($transaction) = @_;
        print "done\n";
    },
);

Many Transactions can be active on one Client at the same time.

The existing pull interface remains available through next_transaction() and next_informational() for integrations that prefer polling.

Server

use Uniform::HTTP::Response;
use Unblock::HTTP3::Server;

my $server = Unblock::HTTP3::Server->new(
    quic => $quic,

    on_request => sub {
        my ($transaction, $request) = @_;

        $transaction->respond(
            Uniform::HTTP::Response->new(
                status => 200,
                body   => "hello\n",
            ),
        );
    },
);

$server->start;

on_body receives request body chunks. on_request_end runs after the request body and trailers have completed.

Streaming bodies

Buffered bodies can live directly on the Uniform message object.

For a streaming client request:

my $transaction = $client->request(
    $request,
    stream_body => 1,
    on_drain => sub {
        my ($transaction) = @_;
        produce_more($transaction);
    },
);

$transaction->write($chunk);
$transaction->end($last_chunk);

A streaming server response uses the same Transaction API:

$transaction->respond(
    $response,
    stream_body => 1,
    on_drain => sub {
        my ($transaction) = @_;
        produce_more($transaction);
    },
);

$transaction->write($chunk);
$transaction->end;

For advanced HTTP/3 body control, Body::Stream and Body::Reader remain available. They expose explicit receive credit, cancellation, and body-specific callbacks without changing the common Transaction API.

Informational responses

A server can send a 1xx response before the final response:

$transaction->send_informational(
    Uniform::HTTP::Response->new(
        status => 103,
    ),
);

$transaction->respond($final_response);

HTTP/3 does not use status 101.

Transaction state

The common lifecycle methods are:

state
error
is_complete
is_cancelled
is_error
is_terminal

HTTP/3-specific reset and STOP_SENDING details remain available separately on the Transaction.

CONNECT, Capsules, and Datagrams

Ordinary CONNECT and generic Extended CONNECT are supported.

Extended CONNECT uses the Uniform protocol field. Higher-level modules decide what a protocol such as WebSocket, WebTransport, or MASQUE means.

An Extended CONNECT Transaction can use the RFC 9297 Capsule Protocol through:

my $capsules = $transaction->capsules;

RFC 9297 HTTP Datagrams use Net::QUIC DATAGRAM support. Enable them on the Client or Server with:

enable_http_datagrams => 1

A client marks a request as using Datagrams with:

my $transaction = $client->request(
    $request,
    datagrams => 1,
);

The Transaction provides send_datagram(), next_datagram(), on_datagram(), and max_datagram_payload_size().

Priority and ORIGIN

RFC 9218 priority state is available through Transaction->priority().

Servers can advertise RFC 9412 origins with the origins constructor option. Clients read the received set with peer_origins().

0-RTT

Net::QUIC owns QUIC/TLS early-data state. Unblock::HTTP3 owns the remembered HTTP/3 SETTINGS needed to validate early requests.

Save the QUIC and HTTP/3 state from the same successful session:

my $quic_state = $quic->early_data_state;
my $h3_state   = $client->peer_settings_state;

An early client request must opt in explicitly:

my $transaction = $client->request(
    $request,
    early_data => 1,
);

0-RTT can be replayed. Unblock::HTTP3 does not automatically retry a rejected early request.

Native integrations

XS event frameworks and HTTP libraries can use Unblock::HTTP3::NativeABI.

Native integrations can discover the installed ABI with:

definition
native_include_dir
header_path
c_header

ABI version 1 accepts exact Unblock::HTTP3::Client, Unblock::HTTP3::Server, and Unblock::HTTP3::Transaction objects. Adapters and subclasses use the portable Perl API.

The native ABI does not expose libnghttp3 internals and does not take ownership of the QUIC transport.

See docs/NATIVE-ABI.md.

What Unblock::HTTP3 does not own

Unblock::HTTP3 does not own:

Those belong to Net::QUIC, the event-loop adapter, the application, or a higher-level protocol module.

Testing

The normal suite uses real kernel UDP sockets, TLS, QUIC, and HTTP/3.

CI tests released dependencies on several Perl versions and validates the built distribution. Separate interoperability tests cover both client and server directions against independent HTTP/3 implementations.

More documentation

License

MIT.