# Unblock::HTTP

Unblock::HTTP is the framework-neutral application layer above HTTP/1,
HTTP/2, HTTP/3, and WebSocket. Protocol parsing stays in the
existing engines. WebSocket sessions use the separate Unblock::WebSocket
distribution; this top layer adds no XS.

Unblock::HTTP 0.02 provides reusable Server attachment, a shared Client
with per-origin pools and bounded queues, and opt-in WebSocket handshakes
over HTTP/1.1 Upgrade and HTTP/2/3 Extended CONNECT. Unblock::WebSocket
is an optional dependency, loaded only when WebSockets are used.
Cross-host Alt-Svc and post-dispatch request replay are intentionally not
provided; optional framework-driven request deadlines are available.
The required released engines are Unblock::HTTP1 0.12+, Unblock::HTTP2
0.12+, Unblock::HTTP3 0.13+, and Uniform::HTTP 0.06+. The separate
[implementation notes](docs/IMPLEMENTATION.md) record development history.

## One Server, many connections

Application code creates one definition:

```perl
use Unblock::HTTP::Server;

my $server = Unblock::HTTP::Server->new(
    http1 => {},
    http2 => {},
    http3 => {},
    on_request => sub {
        my ($tx, $request) = @_;
        $tx->respond(status => 200, body => "hello\n");
    },
);
```

An omitted protocol is disabled. Each protocol hash can carry engine tuning
options. WebSocket remains disabled by default; enable it per protocol
using `websocket => 1` and supply `on_websocket($tx, $request)`.
HTTP/2 and HTTP/3 require a framework `defer($callback)` that schedules
work on a later event-loop turn. The Server advertises Extended CONNECT
only for explicitly enabled protocols. WebSocket acceptance affects
one multiplexed stream on H2/H3, but transfers a whole HTTP1 socket.

`on_headers($tx, $request)` runs first. By default the engine buffers up to
1 MiB, then calls `on_request($tx, $request)` with the complete frozen request.
Set `max_buffered_body_bytes` in a protocol hash to change that positive limit.

For streaming, register `$tx->on_data(sub { my ($tx, $bytes) = @_; ... })`
during `on_headers`. This suppresses full-body accumulation. `on_request`
still runs after successful body completion. Failed or cancelled bodies do
not reach it. Register `$tx->on_finish(...)` before the transaction is terminal
to observe completion, error, or cancellation exactly once.

This requires Unblock::HTTP1 0.12+, Unblock::HTTP2 0.12+, and
Unblock::HTTP3 0.13+. Attachment also checks required engine methods
and gives an explicit error for an incompatible older engine.

## Framework attachment

For each incoming TCP/TLS connection, the adapter attaches its stream:

```perl
my $engine = $server->attach_stream(
    transport => $stream,
    alpn => $negotiated_alpn,
);

$engine->input($bytes);
```

The returned object is the actual HTTP1 or HTTP2 Server engine. Transactions
also come directly from that engine. There is no forwarding connection object.
The framework retains the engine and continues to own its socket and buffers.

The stream supplies `unblock_send`, `unblock_finish`, and `unblock_abort`.
`unblock_send` accepts the entire buffer or throws. A defined false result
means the bytes were accepted but the host is congested. The framework calls
`resume_output()` when it has capacity again.

Configure TLS using `$server->alpn_protocols`. This returns enabled TCP choices,
with H2 preferred. Empty ALPN selects H1 only when H1 is enabled. Unknown or
disabled protocols fail. For cleartext H2 prior knowledge, pass
`protocol => 'h2'` instead of `alpn`.

A ready HTTP/3 QUIC connection uses:

```perl
my $engine = $server->attach_quic(connection => $ready_h3_connection);
```

It returns a started HTTP3 Server engine. UDP and timer events continue to go
to `Net::QUIC::Driver`. The framework is responsible for supplying a connection
negotiated for H3. A QUIC manager can attach all ready connections to the same
definition:

```perl
my $endpoint = Unblock::HTTP::QUIC->server(
    server => $server,
    certificate_file => $cert,
    private_key_file => $key,
    send => $send_datagram,
    set_timeout => $replace_timer,
);
$endpoint->start;
```

Read `perldoc Unblock::HTTP::Integration` for the adapter contract.

## Shared Client

The Client now owns reusable HTTP policy, request scheduling, and per-origin
connection pools. An application makes one Client for many destinations:

```perl
my $op = $client->request(
    method => 'GET',
    url => 'https://example.com/data',
    on_response => sub {
        my ($tx, $response) = @_;
        print $response->status, "\\n";
    },
);
```

The request returns an operation handle right away. Its `transaction()` is
undefined while waiting for a connection, then becomes the **real engine
Transaction** after dispatch. `cancel()`, `state()`, and `error()` work
without polling. A strict `http_version => '3'` requires HTTP/3, not a
fallback to HTTP/2. HTTPS cold requests use H2/H1 ALPN; later requests may use H3
through an authenticated, fresh, same-host Alt-Svc advertisement.

An integration supplies `connect_stream`, `connect_quic` and a `defer`
callback. The factory opens TCP/TLS or QUIC through its framework and calls
`$ready->(...)`. **The ready callback returns the real connected engine.**
The adapter keeps that engine on its framework-owned stream or connection and
feeds input directly to it. The `defer` callback schedules work in a later
event-loop turn. See `perldoc Unblock::HTTP::Client` and
`perldoc Unblock::HTTP::Integration` for the exact contracts.

By default the Client buffers up to 1 MiB of response data in the chosen
engine, then calls `on_response` once after body completion. Supplying
`on_data` automatically streams the response: `on_response` fires when
headers arrive, then `on_data($tx, $chunk)`, `on_end($tx)`, and terminal
`on_finish($tx)`. Client buffering does not add another body accumulator.

A request may set `timeout => $seconds` to cover queuing, establishment,
and its active HTTP transaction. The framework adapter supplies an optional
`schedule_timer($seconds, $callback)` constructor callback returning a timer
cancellation coderef. A timed-out active request cancels its real engine
transaction and reports a timeout on the operation. A factory can return
a cancellation coderef so a timed-out or manually cancelled connection
attempt releases its pool slot; late factory readiness is ignored.

When HTTP/3 QUIC bidi stream credit is exhausted, an unsubmitted
request stays in the bounded Client queue; the H3 engine notifies when
request stream availability changes, and the Client retries admission
only after framework deferral. No submitted request is replayed.

The framework may call `$client->network_changed` when its network
identity changes. This discards idle old-network sockets and Alt-Svc
hints, while active transactions continue on their original engines
until completion. Pending operations are retried only BEFORE dispatch
using fresh connections; a transmitted request is never replayed.
`$client->pool_stats` returns read-only origin and pool counts to
inspect queued, connecting, active, idle, and retiring connections.

Fatal hard-limit output rejection is distinct from cooperative
backpressure. The repository-only Linux::Event integration tests propagate
max_pending_bytes violations as transport errors; it does not
replay the rejected bytes. Unblock::HTTP1 0.12+ waits until attached output is accepted before reporting a
successful server terminal callback. See docs/IMPLEMENTATION.md.

Idle connection eviction is opt-in with `idle_timeout => $seconds`.
When enabled, the framework supplies `schedule_timer` and may supply
`release_connection($host, $engine, $protocol, $reason)` to close its own
socket or QUIC resources. Timers are cancelled on reuse and never close
connections with active streams or queued requests waiting for peer credit.

The repository-only live integration tests also exercise streamed request and
response bodies exceeding 1 MiB on H1/H2/H3, with integrity checks and
controlled accepted-but-blocked output resumption. A preliminary
direct-engine versus unified-Client TCP loopback benchmark is included
at `bench/client-vs-direct.pl`. HTTP/3 additionally has
`bench/h3-client-vs-direct.pl` for serial requests and
`bench/h3-workload-profile.pl` for isolated concurrent CPU, latency
and sampled RSS workloads. Read `docs/BENCHMARKING.md` and
`docs/PERFORMANCE.md` for how to run them and interpret their
limitations. CI correctness tests do not enforce throughput rates.

The queue defaults to 128 pending requests, with at most four connections per
origin and 32 concurrent streams per H2/H3 connection. An H1 connection runs
one request at a time. The limits are adjustable. Pool idle expiry and
WebSocket upgrades/Extended CONNECT are implemented; validated DNS HTTPS
discovery and convenience framework connector classes remain future work. Authenticated same-host Alt-Svc now permits
future requests to select HTTP/3. Advertisements are only learned when
the TCP factory explicitly reports verified_origin => 1 after certificate
and origin hostname verification. When advertised HTTP/3 is selected, the
QUIC factory receives alternative_host and alternative_port and must confirm
verified_origin => 1 before the request can be sent. A failed advertised
QUIC connection may safely fall back before dispatch; a request already
submitted to an engine is never automatically replayed.

## WebSockets across HTTP/1, HTTP/2 and HTTP/3

For each enabled protocol, Server `on_websocket` returns the WebSocket
session options/callbacks, or undef to reject the request with HTTP 403.
The handshake is validated before creating an established
Unblock::WebSocket::Server. H1 uses HTTP 101 and a socket input-consumer
switch; H2/H3 use HTTP 200 and independent streaming CONNECT
transactions on their existing multiplexed connections.

    my $server = Unblock::HTTP::Server->new(
        http2 => { websocket => 1 },
        http3 => { websocket => 1 },
        defer => sub { $framework->defer($_[0]) },
        on_request => sub { $_[0]->respond(status => 200, body => 'HTTP') },
        on_websocket => sub {
            return {
                on_message => sub {
                    my ($ws, $message, $type) = @_;
                    $ws->send_text("echo:$message") if $type eq 'text';
                },
            };
        },
    );

A unified Client selects its WebSocket protocol explicitly with
`http_version => '1.1'`, `'2'`, or `'3'`, or defaults to the
first enabled WebSocket protocol in that order:

    my $operation = $client->connect_websocket(
        url => 'wss://example.com/chat',
        http_version => '3',
        on_open => sub {
            my ($ws, $tx) = @_;
            $ws->send_text('hello');
        },
    );

The operation's `websocket()` accessor holds the actual WebSocket
Client once opened. H2/H3 WebSockets remain in the Client's pool until
their individual streams finish, allowing ordinary HTTP to share the
connection. HTTP3 Client WebSockets wait for the peer's current
Extended CONNECT SETTINGS before dispatch; if QUIC bidi-stream
capacity is unavailable, the original unsent operation remains
queued until credit returns. A denied/invalid handshake fails only
the relevant operation. Consult the [WebSocket cookbook](docs/WEBSOCKET-COOKBOOK.md),
Server and Client POD for running all three protocols, the exact
transport ownership rules, backpressure, Close and callback contracts.

## Examples and tests

The optional Linux::Event examples are:

- `examples/linux-event-server.pl`: a shared Server with H1/H2 attachment.
- `examples/linux-event-quic-server.pl`: a shared Server with a QUIC manager.
- `examples/linux-event-websocket-server.pl`: H1 Upgrade or H2 Extended CONNECT WebSocket echo.
- `examples/linux-event-websocket-client.pl`: matching H1/H2 shared Client transport factory and WebSocket echo.
- `examples/linux-event-quic-websocket-server.pl`: H3 WebSocket echo on QUIC/TLS.

Each separates application behavior from framework plumbing. The core does
not require Linux::Event. The distribution requires Perl 5.20; some
optional example programs use newer Perl syntax. No framework adapter
is installed by this distribution.

Run isolated contract tests:

    prove -lr t

Run the live tests after installing the real engines, Linux::Event and Net::QUIC:

    prove -l xt/linux-event-tcp.t xt/linux-event-websocket.t xt/linux-event-quic.t

The distribution suite combines isolated contract tests with real H1/H2
WebSocket exchanges, a borrowed native H1 input handoff regression,
and full localhost QUIC/TLS H3 WebSocket Server/Client tests.
The optional live integration suite additionally exercises real sockets,
streamed bodies, QUIC flow credit and resource bounds. CI does not
enforce performance or throughput thresholds.

## License

MIT License.
