# Unblock::HTTP1

[![Tests](https://github.com/haxmeister/perl-Unblock-HTTP1/actions/workflows/test.yml/badge.svg)](https://github.com/haxmeister/perl-Unblock-HTTP1/actions/workflows/test.yml)
[![CPAN](https://img.shields.io/cpan/v/Unblock-HTTP1.svg)](https://metacpan.org/release/Unblock-HTTP1)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

Portable, non-blocking HTTP/1 for Perl.

Unblock::HTTP1 is an HTTP/1 protocol engine. It handles HTTP bytes and message
boundaries, but it does not open sockets or choose an event loop.

The same engine can be used with any reliable ordered byte stream.

## What it does

Unblock::HTTP1 provides:

- HTTP/1.0 and HTTP/1.1 client and server protocol state
- request and response parsing
- request and response serialization
- Content-Length and chunked framing
- close-delimited response bodies
- streaming bodies and trailers
- persistent connections
- informational responses
- Expect: 100-continue
- Upgrade and CONNECT boundaries
- configurable protocol limits
- Uniform::HTTP request and response objects

It does not provide sockets, DNS, TLS, connection pools, redirects, cookies,
authentication, proxy policy, WebSocket framing, HTTP/2, or HTTP/3.

## Install

    cpan Unblock::HTTP1

## Client

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

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

    $client->request(
        Uniform::HTTP::Request->new(
            method    => 'GET',
            target    => '/',
            authority => 'example.com',
        ),
        on_response => sub {
            my ($tx, $response) = @_;
            print $response->status, "\n";
        },
        on_body => sub {
            my ($tx, $response, $bytes) = @_;
            print $bytes;
        },
    );

Feed bytes received from your transport:

    $client->input($bytes);

Take generated bytes and send them through your transport:

    while ($client->want_write) {
        my $bytes = $client->output;
        $transport->write($bytes);
    }

When the transport reaches EOF:

    $client->input_eof;

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 ($tx, $request) = @_;

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

Feed received bytes with input() and drain generated bytes with output(), just
as with the client.

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

## Streaming bodies

Pass stream_body when the body length is not known yet:

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

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

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

The same Transaction write()/end() API is used for streaming server responses.

## 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.

## Backpressure

The engine has configurable high-water and low-water output limits.

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

## 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.

## Message objects

Unblock::HTTP1 uses Uniform::HTTP directly:

    Uniform::HTTP::Request
    Uniform::HTTP::Response

It does not define competing HTTP message classes.

Received messages preserve ordered duplicate fields, trailers, exact request
targets, and the received HTTP version.

## Integration

Unblock::HTTP1 does not require a particular event loop or framework.

An adapter only needs to:

1. pass received bytes to input()
2. drain output() while want_write() is true
3. call input_eof() when the transport closes
4. stop feeding HTTP bytes when is_switched() becomes true

See docs/INTEGRATION.md for the transport boundary.

## 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.
