Unblock::HTTP3
Unblock::HTTP3 is a non-blocking HTTP/3 protocol engine for Perl.
HTTP/3 is HTTP carried over QUIC. Unblock::HTTP3 handles the HTTP/3 layer while Net::QUIC handles QUIC and TLS.
application or HTTP library
|
Uniform::HTTP messages
|
Unblock::HTTP3
|
Net::QUIC
|
UDP
Unblock::HTTP3 does not own a UDP socket, timer, TLS configuration, or event loop. Those stay below Net::QUIC, so the HTTP/3 engine can be used with different operating systems and event loops.
Uniform::HTTP supplies the request and response objects. Alien::nghttp3 supplies libnghttp3 for HTTP/3 framing and QPACK.
Installation
From CPAN:
cpanm Unblock::HTTP3
Unblock::HTTP3 0.01 requires:
Perl 5.20+
Alien::nghttp3 0.01+
Net::QUIC 0.04+
Uniform::HTTP 0.04+
Start here
Most code works with three things:
Unblock::HTTP3::Connection- one HTTP/3 connectionUnblock::HTTP3::Transaction- one request and its responseUniform::HTTP::RequestandUniform::HTTP::Response- HTTP messages
Unblock::HTTP3::Request and Unblock::HTTP3::Response are optional thin
subclasses with a few HTTP/3-specific helpers.
An HTTP/3 Connection wraps an existing Net::QUIC::Connection:
use Unblock::HTTP3::Connection;
my $h3 = Unblock::HTTP3::Connection->client(
quic => $quic,
);
$h3->start;
Your event-loop adapter continues to drive Net::QUIC. Unblock::HTTP3 never blocks waiting for network activity.
Sending a request
A client can submit a normal Uniform::HTTP::Request:
use Uniform::HTTP::Request;
my $request = Uniform::HTTP::Request->new(
method => 'GET',
target => '/',
scheme => 'https',
authority => 'example.com',
);
my $tx = $h3->request($request);
When the final response headers arrive, the Transaction becomes available from the Connection:
while (my $ready = $h3->next_transaction) {
my $response = $ready->response;
print $response->status, "\n";
print $response->body if $response->has_buffered_body;
}
Many Transactions can be active at once. Each Transaction keeps its own request and response paired even when responses arrive out of order.
Receiving a request
A server receives new requests as Transactions:
while (my $tx = $h3->next_transaction) {
my $request = $tx->request;
my $response = $tx->response;
$response->status(200);
$response->header('Content-Type', 'text/plain');
$response->body("hello\n");
$tx->send_response;
}
The server-side Response is mutable until it is sent.
Bodies
Buffered bodies are the default.
A buffered request or response body lives on the Uniform message object:
my $body = $response->body;
For large or incremental bodies, use streaming instead.
A server can stream a response:
my $body = $tx->response_body(
on_drain => sub { ... },
on_cancel => sub { ... },
);
$body->write($chunk);
$body->complete;
write() returns false when the bytes were accepted but the producer should
pause until on_drain runs.
A client can receive a response without buffering the complete body:
my $tx = $h3->request(
$request,
receive_body => {
on_data => sub {
my ($reader, $chunk) = @_;
process($chunk);
},
on_end => sub {
my ($reader) = @_;
...
},
},
);
Streaming receive credit is returned as the application consumes data.
Trailers and informational responses
Request and response trailers are supported.
Uniform::HTTP keeps trailers separate from normal headers and preserves field order and duplicates.
Servers can also send 1xx informational responses before the final response:
$tx->send_informational(
Unblock::HTTP3::Response->new(
status => 103,
),
);
HTTP/3 does not use status 101.
CONNECT
Basic CONNECT tunnels are supported.
Generic Extended CONNECT is also supported. A server enables it with:
my $h3 = Unblock::HTTP3::Connection->server(
quic => $quic,
enable_extended_connect => 1,
);
An Extended CONNECT request uses the Uniform protocol field.
Unblock::HTTP3 does not assign meaning to protocol names. Higher-level modules decide what protocols such as WebTransport, WebSocket, or MASQUE mean.
Extended CONNECT Transactions can also use the generic RFC 9297 Capsule Protocol through:
my $capsules = $tx->capsules;
HTTP Datagrams
RFC 9297 HTTP Datagrams are supported over Net::QUIC's QUIC DATAGRAM support.
Enable them on the HTTP/3 connection:
my $h3 = Unblock::HTTP3::Connection->client(
quic => $quic,
enable_http_datagrams => 1,
);
A client marks a request as using HTTP Datagrams when it creates the Transaction:
my $tx = $h3->request(
$request,
datagrams => 1,
);
$tx->send_datagram($bytes);
The Transaction also provides next_datagram, on_datagram, and
max_datagram_payload_size.
The higher-level protocol still decides what the Datagram payload means.
Request priority
RFC 9218 priority can be set on a request:
my $request = Unblock::HTTP3::Request->new(
method => 'GET',
target => '/',
scheme => 'https',
authority => 'example.com',
priority => {
urgency => 1,
incremental => 1,
},
);
It can also be changed on a live Transaction:
$tx->priority(
urgency => 0,
incremental => 0,
);
Urgency is from 0 through 7, where 0 is most urgent.
0-RTT
Net::QUIC owns QUIC/TLS early-data state. Unblock::HTTP3 owns the remembered HTTP/3 SETTINGS needed to decide what can safely be sent before the new server SETTINGS frame arrives.
Save both values from the same successful session:
my $quic_state = $quic->early_data_state;
my $h3_state = $h3->peer_settings_state;
On a resumed connection, give each value back to the layer that created it.
An HTTP/3 request sent before the handshake finishes must opt in explicitly:
my $tx = $h3->request(
$request,
early_data => 1,
);
0-RTT can be replayed. Unblock::HTTP3 does not automatically retry an early request if QUIC rejects it.
See Unblock::HTTP3::Connection and docs/ARCHITECTURE.md for the complete
SETTINGS persistence rules.
Correctness and limits
Unblock::HTTP3 validates HTTP/3 message rules before sending and while receiving.
This includes:
- pseudo-header and routing rules
- Host and
:authority - Content-Length
- trailers
- bodyless responses
- CONNECT rules
- peer field-section limits
It also provides configurable limits for buffered bodies, streaming receive queues, field sections, QPACK, and HTTP Datagram queues.
Protocol errors are kept at the narrowest correct scope when possible. A bad request stream does not automatically destroy unrelated multiplexed requests.
Extensions
The engine provides generic extension hooks without assigning application semantics to them:
- extension SETTINGS
- extension unidirectional streams
- Extended CONNECT protocol names
- Capsules
- HTTP Datagrams
This is the intended foundation for higher-level HTTP/3 protocols.
What Unblock::HTTP3 does not own
Unblock::HTTP3 does not own:
- UDP sockets
- TLS
- QUIC packet processing
- congestion control
- retransmission
- QUIC connection migration
- timers
- event-loop scheduling
- web-framework behavior
Those responsibilities stay in Net::QUIC, the event-loop adapter, or the application.
HTTP/3 Server Push is not exposed because the libnghttp3 version used by this release does not implement it.
Testing
The normal test suite uses real kernel UDP sockets, TLS, QUIC, and HTTP/3.
CI tests released CPAN dependencies on Perl 5.20, 5.28, 5.36, and 5.44 and also validates the built distribution.
Public interoperability tests talk to independent HTTP/3 servers but stay outside normal CPAN installation tests.
More documentation
Unblock::HTTP3::Connection- connection setup and configurationUnblock::HTTP3::Transaction- one request/response streamUnblock::HTTP3::Body::Stream- outgoing streaming bodiesUnblock::HTTP3::Body::Reader- incoming streaming bodiesUnblock::HTTP3::Capsule- RFC 9297 CapsulesUnblock::HTTP3::Extension::Stream- generic extension streamsdocs/ARCHITECTURE.md- protocol ownership and internal data flow
License
MIT.