Unblock::HTTP1

Tests CPAN License

Unblock::HTTP1 is a non-blocking HTTP/1 protocol engine for Perl.

It handles HTTP/1.0 and HTTP/1.1 parsing, serialization, framing, streaming bodies, persistent connections, informational responses, trailers, Upgrade, and CONNECT.

It does not open sockets, perform DNS or TLS, choose an event loop, or manage connection pools.

application or HTTP library
        |
  Uniform::HTTP messages
        |
   Unblock::HTTP1
        |
   byte transport

The transport can be Linux::Event, IO::Async, AnyEvent, Mojolicious, a blocking socket, an in-memory test connection, or something else.

Installation

From CPAN:

cpanm Unblock::HTTP1

Unblock::HTTP1 0.10 requires Perl 5.16 or newer and Uniform::HTTP 0.06 or newer.

Version 0.10 intentionally joins the common Unblock HTTP API vocabulary while the distributions are still young. No compatibility aliases are carried.

Start here

The public API is built around three objects:

HTTP messages are normal Uniform::HTTP::Request and Uniform::HTTP::Response objects.

The application-facing vocabulary is intentionally shared with the other Unblock HTTP engines:

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

Transactions use the common lifecycle vocabulary:

state()
error()
is_complete()
is_cancelled()
is_error()
is_terminal()

The basic transport contract is byte-in, byte-out:

$engine->input($bytes_from_transport);

while ($engine->want_write) {
    my $bytes = $engine->output;
    last unless length $bytes;
    $transport->write($bytes);
}

Unblock::HTTP1 never waits for network activity itself.

Client

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

my $client = Unblock::HTTP1::Client->new;

my $transaction = $client->request(
    Uniform::HTTP::Request->new(
        method    => 'GET',
        target    => '/',
        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";
    },
);

A Client serializes requests on one connection. It does not silently enable HTTP/1 pipelining.

Server

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

my $server = Unblock::HTTP1::Server->new(
    on_request => sub {
        my ($transaction, $request) = @_;

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

Request bodies arrive through on_body. on_request_end runs after the complete request body and trailers have arrived.

Streaming bodies

For a streaming client request:

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

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

For a streaming server response:

$transaction->respond(
    $response,
    stream_body => 1,
);

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

HTTP/1.1 uses chunked framing when needed. HTTP/1.0 streaming requires an explicit Content-Length.

write() returns false when the engine output queue reaches its high-water mark. on_drain fires after the queue falls below its low-water mark.

Informational responses

A server Transaction can send a 1xx response before its final response:

$transaction->send_informational($response);
$transaction->respond($final_response);

Upgrade and CONNECT

A 101 response or successful CONNECT ends HTTP framing on the connection.

The engine then reports:

$engine->is_switched

Bytes already read after the HTTP boundary are preserved:

my $bytes = $engine->take_remainder;

The caller can pass those bytes to the next protocol implementation.

Native integration

The normal input() method remains the portable path.

XS-backed transports can optionally use Unblock::HTTP1::NativeABI to feed borrowed native input buffers directly. The transport keeps ownership of the input storage and Unblock reports the permanently consumed prefix.

Canonical Uniform::HTTP 0.06 messages use the Uniform native construction path on this route.

Native integrations can discover the installed ABI with:

my $definition = Unblock::HTTP1::NativeABI::definition();
my $include_dir = Unblock::HTTP1::NativeABI::native_include_dir();
my $header_path = Unblock::HTTP1::NativeABI::header_path();
my $header = Unblock::HTTP1::NativeABI::c_header();

The installed public header is:

Unblock/HTTP1/NativeABI/unblock_http1_native_abi.h

HTTP/1 keeps its native ABI focused on borrowed input. It does not add HTTP/2-specific native output operations merely for API symmetry.

See docs/INTEGRATION.md for the transport contract and ownership rules.

Limits

Default limits are:

maximum HTTP head:             65536 bytes
maximum header fields:         100
maximum chunk extension bytes: 16384 per message
output high water:             65536 bytes
output low water:              32768 bytes

The limits can be changed when constructing a Client or Server.

Protocol status

The HTTP/1 protocol engine is complete for its declared scope and has cross-platform CI coverage on Linux, macOS, and Windows, including Perl 5.16.

See docs/PROTOCOL_STATUS.md for the detailed protocol checklist.

License

MIT License.