NAME

Fetch - HTTP/2 Future-based user agent

VERSION

Version 0.01

SYNOPSIS

use Fetch;

# works out of the box - no event loop to set up
my $res = Fetch->new->get('https://example.com')->get;
print $res->content if $res->is_success;

# every call returns a Fetch::Future; ->get awaits it
my $ua  = Fetch->new(timeout => 10);
my $f   = $ua->post('https://api/things',
                    headers => { 'Content-Type' => 'application/json' },
                    body    => '{"name":"x"}');
my $res = $f->get;

# many requests concurrently on one loop
my @futs = map { $ua->get($_) } @urls;
Fetch::Future->needs_all(@futs)->get;
my @bodies = map { $_->get->content } @futs;

# a live WebSocket
my $ws = $ua->websocket('wss://host/socket')->get;
$ws->send('hello');
my $reply = $ws->next_message->get;

DESCRIPTION

Fetch is an HTTP user agent whose socket, TLS, HTTP/2 framing and HTTP/1.1 parsing hot path lives in vendored C, and whose asynchronous results are Fetch::Future objects that compose with the Hyperman event loop and other CPAN loops (IO::Async, AnyEvent) - or with nothing at all, since Fetch ships its own event loop (Fetch::Loop::Standalone) and uses it automatically.

HTTP/1.1 and HTTP/2 (ALPN-negotiated over TLS), over cleartext and TLS, with keep-alive connection pooling, redirect following, per-request timeouts, streaming response bodies, a cookie jar, JSON request/response helpers, and native WebSockets. Every request method returns a Fetch::Future; ->get on one awaits it, pumping whichever event loop is active (its own if none), so the same code serves both a simple synchronous call and thousands of requests multiplexed on one loop.

CONSTRUCTOR

new(%args)

my $ua = Fetch->new(
    timeout    => 10,
    tls_verify => 1,
    headers    => { 'Accept' => 'application/json' },
    cookie_jar => 1,
);

Create a user agent. All arguments are optional:

loop

The event loop to run on. Omit it and Fetch uses its own Fetch::Loop::Standalone (so ->get just works with no framework). Pass a raw IO::Async::Loop or Hyperman::Loop and it is wrapped automatically; pass the string 'AnyEvent' to drive AnyEvent; or pass a ready-made Fetch::Loop adapter. See "EVENT LOOPS".

headers

Default headers sent on every request, as a hashref, an arrayref of name => value pairs (duplicates preserved), or a Fetch::Headers. Per-request headers are merged on top (see "REQUEST OPTIONS").

agent

The User-Agent string. Defaults to "Fetch/$VERSION".

tls_verify

Whether to verify the peer certificate and hostname for https. Default true. Overridable per request.

timeout

Default per-request deadline in seconds (fractional allowed). 0 (the default) means no timeout. Overridable per request.

max_redirects

How many redirects to follow. Default 5; 0 disables following. Overridable per request.

keep_alive

Reuse connections via a keep-alive pool (default true). Set false to close every connection after one request.

pool_size

Maximum idle connections the keep-alive pool parks (default 32).

A Fetch::CookieJar to store and send cookies (applied across redirects), or a true scalar to create a fresh one. Default: no jar.

REQUEST METHODS

Each returns a Fetch::Future that resolves to a Fetch::Response (or fails with an error string). They never block; call ->get on the future to await the result.

get / head / delete

my $f = $ua->get($url, %opt);

post / put

my $f = $ua->post($url, body => $bytes, %opt);

request($method, $url, %opt)

my $f = $ua->request('PATCH', $url, body => $bytes, %opt);

The general form the verb helpers dispatch to; use it for any method.

REQUEST OPTIONS

Passed as a trailing key => value list to any request method:

headers

Extra headers for this request - a hashref, an arrayref of pairs (keeping duplicate names, e.g. multiple X-* values), or a Fetch::Headers. Each named field overrides the agent default of the same name.

body

The request body (bytes). Content-Length is added automatically unless you set it yourself.

json

A Perl data structure to send as a JSON body: it is encoded and Content-Type: application/json is set (unless you gave your own). Takes precedence over body. Pair it with "json" in Fetch::Response to decode the reply. Encoding uses Cpanel::JSON::XS when installed, else core JSON::PP.

my $res  = $ua->post($url, json => { name => 'x', ok => \1 })->get;
my $data = $res->json;
timeout

Override the agent timeout for this request (seconds; 0 disables it).

tls_verify

Override certificate/hostname verification for this https request.

max_redirects

Override how many redirects this request follows.

on_body

A coderef called with each body chunk as it arrives, instead of buffering. Suits large downloads and server-sent events: the buffer is compacted so an endless stream does not grow memory, and the resolved response body is empty.

$ua->get($url, on_body => sub { my ($chunk) = @_; print $chunk })->get;

WEBSOCKETS

websocket($url, %opt)

my $ws = $ua->websocket('ws://host/echo')->get;   # or wss://

Open a WebSocket (RFC 6455). Returns a Fetch::Future that resolves, after the 101 handshake, to a Fetch::WebSocket for sending and receiving messages. Accepts ws:///wss:// (and http/https); tls_verify and timeout options apply to the handshake.

ACCESSORS

loop

The event-loop adapter this agent runs on.

The Fetch::CookieJar in use, or undef.

THE RESULT

Awaiting a request future yields a Fetch::Response with status, headers (a Fetch::Headers), content, header($name), is_success and is_redirect. A failed request (connection error, timeout, bad TLS, a rejected WebSocket upgrade) fails the future with a message; ->get rethrows it, or inspect $f->failure / $f->is_failed.

EVENT LOOPS

$future->get awaits by pumping the active loop, so a bare synchronous call needs no setup. Hand Fetch a loop to cooperate with an existing async program - requests then fly concurrently on that one loop without blocking it:

use IO::Async::Loop;
my $loop = IO::Async::Loop->new;
my $ua   = Fetch->new(loop => $loop);       # shares this loop
my $f    = $ua->get('https://example.com/');
$loop->loop_once until $f->is_ready;
my $res  = $f->get;

Supported loops: the built-in Fetch::Loop::Standalone, plus Fetch::Loop::IOAsync, Fetch::Loop::AnyEvent and Fetch::Loop::Hyperman.

SEE ALSO

Fetch::Response, Fetch::Headers, Fetch::CookieJar, Fetch::WebSocket, Fetch::Future, Fetch::Loop and the loop adapters Fetch::Loop::Standalone, Fetch::Loop::IOAsync, Fetch::Loop::AnyEvent, Fetch::Loop::Hyperman.

AUTHOR

LNATION <email@lnation.org>

LICENSE AND COPYRIGHT

This software is Copyright (c) 2026 by LNATION.

This is free software, licensed under the Artistic License 2.0.