Linux::Event::HTTP
Linux::Event::HTTP is a native HTTP communications layer for Linux::Event.
Linux::Event owns sockets, TLS, readiness, buffering, backpressure, and ordered byte output. Linux::Event::HTTP owns HTTP parsing, framing, persistence, serialization, client policy, and HTTP exchange lifecycle. It is deliberately not a web framework.
Server
use v5.36;
use Linux::Event::Loop;
use Linux::Event::HTTP::Server;
my $loop = Linux::Event::Loop->new;
my $server = Linux::Event::HTTP::Server->new(
loop => $loop,
host => '127.0.0.1',
port => 8080,
on_request => sub ($conn, $req, $res) {
$res->header('Content-Type', 'text/plain');
$res->body("hello\n");
},
);
$loop->run;
The callback receives the persistent HTTP connection, one Request, and the
Response paired with that Request. The active exchange is available as
$conn->transaction when lifecycle or incremental-body operations are needed.
Client
use v5.36;
use Linux::Event::Loop;
use Linux::Event::HTTP::Client;
my $loop = Linux::Event::Loop->new;
my $client = Linux::Event::HTTP::Client->new(loop => $loop);
my $operation = $client->get(
'https://example.com/items?limit=10',
on_response => sub ($tx, $res) {
say $res->status;
},
on_body => sub ($tx, $res, $bytes) {
process_bytes($bytes);
},
on_complete => sub ($tx) {
$client->close;
$loop->stop;
},
on_error => sub ($tx, $error) {
warn $error;
$client->close;
$loop->stop;
},
);
$loop->run;
High-level Client methods return Linux::Event::HTTP::Client::Operation.
An operation normally contains one Linux::Event::HTTP::Transaction. Followed
redirects and automatic authentication retries create additional Transactions
because a Transaction always means exactly one Request/Response exchange.
my $tx = $operation->transaction;
my $req = $operation->request;
my $res = $operation->response;
Low-level Linux::Event::HTTP::Client::Connection->request() returns one
Transaction directly.
Message and lifecycle model
client sends Request -----> server receives Request
client gets Response <----- server sends Response
Request / Response = direction-neutral HTTP messages
Transaction = exactly one Request + one Response + exchange lifecycle
Client::Operation = one high-level client action, possibly several Transactions
Connection = one persistent transport executing Transactions
A locally constructed message is mutable until protocol commit. Received message metadata is committed/read-only. Request contains an HTTP request-target, not a full application URL; URL and routing policy belong to the high-level Client.
Bodies
A complete scalar body belongs to the message:
$res->body($bytes);
Incremental server Response production belongs to Transaction:
my $body = $conn->transaction->response_body(
on_drain => sub ($body) { ... },
on_cancel => sub ($body) { ... },
);
$body->write($bytes);
$body->complete;
Incremental client Request production uses the same producer model:
my $operation = $client->post(
$url,
stream_body => {
on_drain => sub ($body) { ... },
on_cancel => sub ($body) { ... },
},
);
my $body = $operation->request_body;
$body->write($bytes);
$body->complete;
Linux::Event remains the only output queue. Body::Stream->write follows the
Linux::Event flow-control contract:
true = bytes accepted; producer may continue
false = bytes accepted; pause until on_drain
Incoming bodies are incremental-first. If no consumer is installed, body bytes are drained rather than accumulated implicitly. Small client Responses can opt into bounded whole-body buffering:
$client->get(
$url,
buffer_body => 1_048_576,
on_complete => sub ($tx) {
my $bytes = $tx->response->body;
...;
},
);
There is no implicit unbounded whole-response buffer.
Redirects
The Client follows 301, 302, 303, 307, and 308 by default with a limit of five
redirects. max_redirects => 0 disables redirect interpretation.
my $operation = $client->get(
$url,
max_redirects => 5,
on_redirect => sub ($op, $tx, $res, $next_url) {
say "redirecting to $next_url";
},
);
Every followed redirect is a distinct Transaction retained by the Operation. 301/302 may change POST to GET, 303 uses GET except for HEAD, and 307/308 preserve method and body. Complete scalar bodies can be replayed; streaming producers are not assumed to be rewindable.
Cross-origin redirects remove caller-supplied Authorization and Cookie.
Connection-specific fields are regenerated rather than forwarded verbatim.
When a cookie jar is configured, Cookie is regenerated independently for each
hop from the new target URL. Uniform-managed authentication fields are also
attempt-local and are regenerated only after a new challenge.
Cookies
Cookie policy is provided by HTTP::CookieJar rather than implemented by
Linux::Event::HTTP. Applications explicitly create and own the jar:
use HTTP::CookieJar;
my $jar = HTTP::CookieJar->new;
my $client = Linux::Event::HTTP::Client->new(
loop => $loop,
cookie_jar => $jar,
);
Before each ordinary request exchange, Client asks the jar for cookies using the
target URL. Every Set-Cookie field from final, redirect, and authentication
challenge Responses is fed back to the jar using that same target URL before
higher-level policy or application response processing continues.
This distinction matters with proxies: the proxy is only the route. It never
becomes the cookie origin merely because the TCP or TLS connection terminates
there. Redirects ask the jar again for the new target URL, leaving domain, path,
expiry, Secure handling, and cookie ordering to HTTP::CookieJar.
Linux::Event::HTTP does not create an implicit jar. Jar lifetime, sharing,
persistence, preloading, and clearing remain application policy. When a jar is
configured, caller-supplied Cookie fields are rejected so cookie selection has
one owner; seed or alter cookies through the jar itself.
connect_tunnel() does not consult the cookie jar because CONNECT is an explicit
exchange with the named proxy endpoint followed by protocol handoff, not an
ordinary target-resource request.
Authentication
HTTP authentication mechanics are provided by Uniform::HTTP::Auth rather than
implemented in Linux::Event::HTTP. It supports Basic, Bearer, and Digest while
remaining independent of any HTTP client or event loop.
use Uniform::HTTP::Auth;
my $auth = Uniform::HTTP::Auth->new(
credentials => sub ($context) {
return $credential_store->lookup(
$context->{origin},
$context->{realm},
$context->{scheme},
);
},
);
my $client = Linux::Event::HTTP::Client->new(
loop => $loop,
auth => $auth,
proxy_auth => $auth,
);
auth handles target 401 / WWW-Authenticate. proxy_auth handles proxy
407 / Proxy-Authenticate. Linux::Event::HTTP gives Uniform the challenge,
protection-space origin, method, and exact request-target. Uniform returns the
complete authentication field value; Linux::Event::HTTP decides whether the
Request can be replayed and creates another Transaction when it can.
max_auth_retries defaults to three and is independent of max_redirects.
max_auth_retries => 0 disables automatic challenge retry. The Operation keeps
all Transactions and exposes auth_retry_count; authentication retries do not
increase redirect_count.
Complete scalar Request bodies can be replayed and are supplied to Uniform for
Digest qop=auth-int. Streaming Request producers are never automatically
replayed, even if the producer has already completed, because application stream
state is not inherently rewindable.
Authentication identity follows the same target/route split as the rest of the Client. A target 401 uses the target origin; a proxy 407 uses the selected proxy route origin. A request can therefore answer a proxy 407 and then a target 401 without confusing the two protection spaces.
When auth is configured, it owns Authorization; when proxy_auth is
configured, it owns Proxy-Authorization. Manual fields remain available by
disabling the corresponding manager for that request.
Forward proxies
A Client can configure one explicit default forward proxy:
my $client = Linux::Event::HTTP::Client->new(
loop => $loop,
proxy => 'http://proxy.example:3128',
);
$client->get('http://origin.example/path');
One operation can override the Client default:
$client->get(
'http://origin.example/path',
proxy => 'http://other-proxy.example:3128',
);
Or explicitly bypass it:
$client->get(
'http://origin.example/path',
proxy => undef,
);
Direct requests use origin-form targets such as /path?x=1. Proxied ordinary
HTTP/1 requests use absolute-form targets such as
http://origin.example/path?x=1. Host is always derived from the target URL in
proxy mode.
The target origin remains the redirect, cookie, and target-authentication identity. The route origin selects the actual connection, idle-pool entry, and proxy-authentication identity. Sequential requests to different target origins can therefore reuse one persistent proxy connection without sharing target cookies or target credentials.
Proxy endpoints may use http or https. TLS to an HTTPS proxy terminates at
the proxy. An HTTPS target URI used with ordinary proxy routing is still sent
in absolute-form; it is not a hidden CONNECT tunnel and does not create
end-to-end TLS to that target.
There is no automatic environment-proxy discovery, PAC/NO_PROXY policy, or SOCKS behavior.
CONNECT tunnels
Client CONNECT is explicit:
my $operation = $client->connect_tunnel(
'http://proxy.example:3128',
'target.example:443',
tunnel_to => 'MyTunnelProtocol',
proxy_auth => $auth,
on_tunnel => sub ($op, $tx, $res, $connection) {
...;
},
);
The proxy endpoint and tunnel target are separate. connect_tunnel() uses the
Client's proxy_auth by default and can override or disable it per call. A 407
may therefore be drained and retried before the tunnel is established.
Any successful 2xx response ends HTTP framing at the response-head boundary;
already-read following bytes become tunnel input and the same Linux::Event
stream transitions to tunnel_to. Non-2xx responses remain ordinary HTTP.
Server CONNECT arrives through the normal request callback and is accepted explicitly through Transaction:
on_request => sub ($conn, $req, $res) {
if ($req->method eq 'CONNECT') {
$conn->transaction->tunnel('MyTunnelProtocol');
return;
}
...;
};
Linux::Event::HTTP owns the CONNECT handshake and live-stream handoff. It does
not decide destination authorization, create the upstream server connection, or
relay bytes between two streams. See docs/CONNECT.md for the full lifecycle and
validation rules.
Upgrade
Server Upgrade is a Transaction lifecycle operation:
$res->header('Upgrade', 'my-protocol');
$conn->transaction->upgrade('MyProtocolConnection');
Client Upgrade explicitly names the target stream class:
my $operation = $client->get(
$url,
headers => [
[ Connection => 'Upgrade' ],
[ Upgrade => 'my-protocol' ],
],
upgrade_to => 'MyProtocolConnection',
on_upgrade => sub ($op, $tx, $res, $connection) {
...;
},
);
A validated 101 completes the HTTP Transaction before Linux::Event
transition_to() hands the same live stream to the next protocol. Already-read
post-HTTP bytes are preserved.
HTTPS
HTTPS uses the same Client, Server, Request, Response, Transaction, Operation, and Connection classes. TLS remains Linux::Event transport policy; there is no parallel HTTPS hierarchy.
Server TLS is configured on Server->new(tls => {...}). Direct Client HTTPS
uses the target URL host as the TLS server name and currently advertises only
http/1.1 through ALPN. An HTTPS proxy endpoint uses TLS to the proxy itself.
Cookie and target-authentication origin identity remain the target URL; proxy
authentication identity remains the route endpoint.
Connection reuse
The initial HTTP/1 reuse policy is deliberately bounded:
- one active Transaction per Client::Connection;
- no HTTP/1 pipelining on the client;
- sequential keep-alive reuse;
- at most one idle connection retained per route origin;
- concurrent operations may open additional connections;
- extra connections close when they later become idle;
- connections that leave HTTP through Upgrade or successful CONNECT never return to the HTTP idle pool.
Server::Connection supports ordered persistent request processing and deferred responses on the same accepted HTTP/1 connection.
Advanced connection subclasses
Most programs use Server and Client directly. Advanced transport extension
points are:
Linux::Event::HTTP::Server::Connection
Linux::Event::HTTP::Client::Connection
Both are Linux::Event stream-socket subclasses. Custom classes can provide reusable socket or tuning policy without changing Request, Response, Transaction, or Operation APIs.
Public modules
Linux::Event::HTTP
Linux::Event::HTTP::Client
Linux::Event::HTTP::Client::Operation
Linux::Event::HTTP::Client::Connection
Linux::Event::HTTP::Server
Linux::Event::HTTP::Server::Connection
Linux::Event::HTTP::Request
Linux::Event::HTTP::Response
Linux::Event::HTTP::Transaction
Linux::Event::HTTP::Body::Stream
HTTP/1-specific native work remains consolidated in the private
Linux::Event::HTTP::_HTTP1 extension. Client response-head parsing remains
strict Perl code unless measurement demonstrates that another native boundary is
worth maintaining.
Build and test
perl Makefile.PL
make
make test
Linux::Event::HTTP currently requires Linux::Event 0.113 or newer, uses
HTTP::CookieJar for cookie policy, and uses Uniform::HTTP::Auth for HTTP
authentication mechanics.
Design documents
docs/ARCHITECTURE.md- ownership, lifecycle, framing, pooling, and native boundaries.docs/CONNECT.md- client and server CONNECT validation and handoff semantics.docs/CLIENT-POLICY.md- implemented and deliberately deferred client policy.docs/BENCHMARKING.md- benchmark discipline and interpretation.docs/PICOHTTPPARSER-EXPERIMENT.md- parser provenance and representation experiments.
Scope
Linux::Event::HTTP is an HTTP communications layer. It does not include routing, middleware, sessions, templates, PSGI, PAGI, or general web-framework responsibilities. Reusable low-level socket, buffering, backpressure, and transport performance work belongs in Linux::Event core.