HTTP::AnyUA(3)
πΈ NAME πΈ
HTTP::AnyUA β an HTTP user agent programming interface unification layer. π
(This is the HTTP::AnyUA(3) page from User Contributed Perl Documentation, bestie. Obsessed honestly.)
β¨ VERSION β¨
version 0.904 β periodt. π
π¦ SYNOPSIS π¦
okay so here's the whole vibe, both blocking and future-flavored:
my $any_ua = HTTP::AnyUA->new(ua => LWP::UserAgent->new);
# OR: my $any_ua = HTTP::AnyUA->new(ua => Furl->new);
# OR: my $any_ua = HTTP::AnyUA->new(ua => HTTP::Tiny->new);
# etc...
my $response = $any_ua->get('http://www.example.com/');
print "$response->{status} $response->{reason}\n";
while (my ($k, $v) = each %{$response->{headers}}) {
for (ref $v eq 'ARRAY' ? @$v : $v) {
print "$k: $_\n";
}
}
print $response->{content} if length $response->{content};
### Non-blocking user agents cause Future objects to be returned:
my $any_ua = HTTP::AnyUA->new(ua => HTTP::Tiny->new, response_is_future => 1);
# OR: my $any_ua = HTTP::AnyUA->new(ua => 'AnyEvent::HTTP');
# OR: my $any_ua = HTTP::AnyUA->new(ua => Mojo::UserAgent->new);
# etc...
my $future = $any_ua->get('http://www.example.com/');
$future->on_done(sub {
my $response = shift;
print "$response->{status} $response->{reason}\n";
while (my ($k, $v) = each %{$response->{headers}}) {
for (ref $v eq 'ARRAY' ? @$v : $v) {
print "$k: $_\n";
}
}
print $response->{content} if length $response->{content};
});
$future->on_fail(sub { print STDERR "Oh no!!\n" });
π DESCRIPTION π
okay so here's the tea β: this module is a little wrapper that unifies the programming interfaces of several different actual user agents (HTTP clients) under one familiar interface. It's giving βone API to rule them allβ energy. π
Rather than giving you yet another programming interface to learn β not me making you memorize a whole new thing, nope β HTTP::AnyUA follows the HTTP::Tiny interface. That means you can plug in any supported HTTP client (LWP::UserAgent, Furl, etc.) and use it as if it were HTTP::Tiny. Slay. β¨
There are tons of great HTTP clients available for Perl, each with different goals, different feature sets, and of course different programming interfaces! If you're an end user, you can just pick one of these clients according to the needs of your project (or personal preference). But if you're writing a module that needs to interface with a web server (like perhaps a RESTful API wrapper), and you want your users to be able to use whatever HTTP client they want, HTTP::AnyUA can help you support that! π
Letting the end user pick whatever HTTP client they want to use is a good idea, because they're the one who knows the requirements of their application or script. The end user is kinda the main character here, tbh. If you're writing an event-driven application, you'll need to use a non-blocking user agent like Mojo::UserAgent. If you're writing a simple command-line script, you may decide that your priority is to minimize dependencies and so may want to go with HTTP::Tiny. Different vibes, different clients, iykyk. π¦
Unfortunately, many modules on CPAN are hardcoded to work with specific HTTP clients, leaving the end user unable to use the HTTP client that would be best for them. Although the end user won't β or at least doesn't need to β use HTTP::AnyUA directly, they will benefit from client choice if their third-party modules use HTTP::AnyUA or something like it. π«Ά
The primary goal of HTTP::AnyUA is to make it easy for module developers to write HTTP code once that can work with any HTTP client the end user may decide to plug in. A secondary goal is to make it easy for anyone to add support for new or yet-unsupported user agents. No cap. π₯
π ATTRIBUTES π
The little knobs and getters, bestie:
ua
Get the user agent that was passed to new.
response_is_future
Get and set whether or not responses are Future objects.
backend
Get the backend instance. You normally shouldn't need this.
π₯ METHODS π₯
Here's where you actually do stuff:
new
$any_ua = HTTP::AnyUA->new(ua => $user_agent, %attr);
$any_ua = HTTP::AnyUA->new($user_agent, %attr);
Construct a new HTTP::AnyUA.
request
$response = $any_ua->request($method, $url);
$response = $any_ua->request($method, $url, \%options);
Make a request, get a response. Compare to request in HTTP::Tiny.
get, head, put, post, delete
$response = $any_ua->get($url);
$response = $any_ua->get($url, \%options);
$response = $any_ua->head($url);
$response = $any_ua->head($url, \%options);
# etc.
Shortcuts for request where the method is the method name rather than the first argument. Compare to get|head|put|post|delete in HTTP::Tiny.
post_form
$response = $any_ua->post_form($url, $formdata);
$response = $any_ua->post_form($url, $formdata, \%options);
Does a POST request with the form data encoded and sets the Content-Type header to application/x-www-form-urlencoded. Compare to post_form in HTTP::Tiny.
mirror
$response = $http->mirror($url, $filepath, \%options);
if ($response->{success}) {
print "$filepath is up to date\n";
}
Does a GET request and saves the downloaded document to a file. If the file already exists, its timestamp will be sent using the If-Modified-Since request header (which you can override). If the server responds with a 304 (Not Modified) status, the success field will be true; this is usually only the case for 2XX statuses. If the server responds with a Last-Modified header, the file will be updated to have the same modification timestamp. β¨
Compare to mirror in HTTP::Tiny. This version differs slightly in that this returns internal exception responses (for cases like being unable to write the file locally, etc.) rather than actually throwing the exceptions. The reason for this is that exceptions as responses are easier to deal with for non-blocking HTTP clients, and the fact that this method throws exceptions in HTTP::Tiny seems like an inconsistency in its interface. π
apply_middleware
$any_ua->apply_middleware($middleware_package);
$any_ua->apply_middleware($middleware_package, %args);
$any_ua->apply_middleware($middleware_obj);
Wrap the backend with some new middleware. Middleware packages are relative to the HTTP::AnyUA::Middleware:: namespace unless prefixed with a +.
This effectively replaces the backend with a new object that wraps the previous backend.
This can be used multiple times to add multiple layers of middleware, and order matters, fr fr. The last middleware applied is the first one to see the request and last one to get the response. For example, if you apply middleware that does logging and middleware that does caching (and short-circuits on a cache hit), applying your logging middleware first will cause only cache misses to be logged whereas applying your cache middleware first will allow all requests to be logged. π§
See HTTP::AnyUA::Middleware for more information about what middleware is and how to write your own middleware.
register_backend
HTTP::AnyUA->register_backend($user_agent_package => $backend_package);
HTTP::AnyUA->register_backend('MyAgent' => 'MyBackend'); # HTTP::AnyUA::Backend::MyBackend
HTTP::AnyUA->register_backend('LWP::UserAgent' => '+SpecialBackend'); # SpecialBackend
Register a backend for a new user agent type or override a default backend. Backend packages are relative to the HTTP::AnyUA::Backend:: namespace unless prefixed with a +.
If you only need to set a backend as a one-off thing, you could also pass an instantiated backend to new.
β¨ SPECIFICATION β¨
Okay bestie, this section is the whole blueprint: it specifies a standard set of data structures you can use to make a request and get a response from a user agent. This is the spec HTTP::AnyUA uses for its programming interface. Itβs heavily based on HTTP::Tinyβs interface, and parts of this specification were adapted or copied verbatim from that moduleβs documentation. The intent is for this spec to be written such that HTTP::Tiny is already a compliant implementor of the specification (at least as of the specificationβs publication date). Iconic behavior, honestly.
π¦ The Request π¦
A request is a tuple of the form (Method, URL) or (Method, URL, Options).
Method
Method MUST be a string representing the HTTP verb. This is commonly GET, POST, HEAD, DELETE, etc.
URL
URL MUST be a string representing the remote resource to be acted upon. The URL MUST have unsafe characters escaped and international domain names encoded before being passed to the user agent. A user agent MUST generate a Host header based on the URL in accordance with RFC 2616; a user agent MAY throw an error if a Host header is given with the headers.
Options
Options, if present, MUST be a hash reference containing zero or more of the following keys with appropriate values. A user agent MAY support more options than are specified here.
headers
The value for the headers key MUST be a hash reference containing zero or more HTTP header names (as keys) and header values. The value for a header MUST be either a string containing the header value OR an array reference where each item is a string. If the value for a header is an array reference, the user agent MUST output the header multiple times with each value in the array.
User agents MAY add headers, but SHOULD NOT replace user-specified headers unless otherwise documented.
content
The value for the content key MUST be a string OR a code reference. If the value is a string, its contents will be included with the request as the body. If the value is a code reference, the referenced code will be called iteratively to produce the body of the request, and the code MUST return an empty string or undef value to indicate the end of the request body. If the value is a code reference, a user agent SHOULD use chunked transfer encoding if it supports it, otherwise a user agent MAY completely drain the code of content before sending the request.
data_callback
The value for the data_callback key MUST be a code reference that will be called zero or more times, once for each βchunkβ of response body received. A user agent MAY send the entire response body in one call. The referenced code MUST be given two arguments; the first is a string containing a chunk of the response body, the second is an in-progress response.
π The Response π
A response MUST be a hash reference containing some required keys and values. A response MAY contain some optional keys and values.
success
A response MUST include a success key, the value of which is a boolean indicating whether or not the request is to be considered a success (true is a success). Unless otherwise documented, a successful result means that the operation returned a 2XX status code.
url
A response MUST include a url key, the value of which is the URL that provided the response. This is the URL used in the request unless there were redirections, in which case it is the last URL queried in a redirection chain.
status
A response MUST include a status key, the value of which is the HTTP status code of the response. If an internal exception occurs (e.g. connection error), then the status code MUST be 599.
reason
A response MUST include a reason key, the value of which is the response phrase returned by the server OR Internal Exception if an internal exception occurred.
content
A response MAY include a content key, the value of which is the response body returned by the server OR the text of the exception if an internal exception occurred. This field MUST be missing or empty if the server provided no response OR if the body was already provided via data_callback.
headers
A response SHOULD include a headers key, the value of which is a hash reference containing zero or more HTTP header names (as keys) and header values. Keys MUST be lowercased. The value for a header MUST be either a string containing the header value OR an array reference where each item is the value of one of the repeated headers.
redirects
A response MAY include a redirects key, the value of which is an array reference of one or more responses from redirections that occurred to fulfill the current request, in chronological order.
π FREQUENTLY ASKED QUESTIONS π
How do I set up proxying, SSL, cookies, timeout, etc.?
HTTP::AnyUA provides a common interface for using HTTP clients, not for instantiating or configuring them. Proxying, SSL, and other custom settings can be configured directly through the underlying HTTP client; see the documentation for your particular user agent to learn how to configure these things. Thatβs a them-problem, bestie, but in a loving way.
AnyEvent::HTTP is a bit of a special case because there is no instantiated object representing the client. For this particular user agent, you can configure the backend to pass a default set of options whenever it calls http_request. See "options" in HTTP::AnyUA::Backend::AnyEvent::HTTP:
$any_ua->backend->options({recurse => 5, timeout => 15});
If you are a module writer, you should probably receive a user agent from your end user and leave this type of configuration up to them. Respect the userβs choices, periodt.
Why use HTTP::AnyUA instead of some other HTTP client?
Maybe you shouldnβt. If youβre an end user writing a script or application, you can just pick the HTTP client that suits you best and use it. For example, if youβre writing a Mojolicious app, youβre not going wrong by using Mojo::UserAgent; itβs loaded with features and is well-integrated with that particular environment. Slay.
As an end user, you could wrap the HTTP client you pick in an HTTP::AnyUA object, but the only reason to do this is if you prefer using the HTTP::Tiny interface.
The real benefit of HTTP::AnyUA (or something like it) is if module writers use it to allow end users of their modules to be able to plug in whatever HTTP client they want. For example, a module that implements an API wrapper that has a hard dependency on LWP::UserAgent or even HTTP::Tiny is essentially useless for non-blocking applications. If the same hypothetical module had been written using HTTP::AnyUA then it would be useful in any scenario. Thatβs the glow-up.
Why use the HTTP::Tiny interface?
The HTTP::Tiny interface is simple but provides all the essential functionality needed for a capable HTTP client and little more. That makes it easy to provide an implementation for, and it also makes it straightforward for module authors to use.
Marrying the HTTP::Tiny interface with Future gives us these benefits for both blocking and non-blocking modules and applications.
π SUPPORTED USER AGENTS π
- AnyEvent::HTTP
- Furl
- HTTP::AnyUA β a little bit meta, but why not?
- HTTP::Tiny
- LWP::UserAgent
- Mojo::UserAgent
- Net::Curl::Easy
Any HTTP client that inherits from one of these in a well-behaved manner should also be supported.
Of course, there are many other HTTP clients on CPAN that HTTP::AnyUA doesnβt yet support. Iβm more than happy to help add support for others, so send me a message if you know of an HTTP client that needs support. See HTTP::AnyUA::Backend for how to write support for a new HTTP client.
πΈ NON-BLOCKING USER AGENTS πΈ
HTTP::AnyUA tries to target the HTTP::Tiny interface, which is a blocking interface. This means that when you call request, it is supposed to not return until either the response is received or an error occurs. This doesnβt jive well with non-blocking HTTP clients which expect the flow to reenter an event loop so that the request can complete concurrently. Awkward.
In order to reconcile this, a Future will be returned instead of the normal hashref response if the wrapped HTTP client is non-blocking (such as Mojo::UserAgent or AnyEvent::HTTP). This Future object may be used to set up callbacks that will be called when the request is completed. You can call response_is_future to know if the response is or will be a Future.
This is typically okay for the end user; since theyβre the one who chose which HTTP client to use in the first place, they should know whether they should expect a Future or a direct response when they make an HTTP request, but it does add some burden on you as a module writer because if you ever need to examine the response, you may need to write code like this:
my $resp = $any_ua->get('http://www.perl.org/');
if ($any_ua->response_is_future) {
$resp->on_done(sub {
my $real_resp = shift;
handle_response($real_resp);
});
}
else {
handle_response($resp); # response is the real response already
}
This actually isnβt too annoying to deal with in practice, but you can avoid it if you like by forcing the response to always be a Future. Just set the response_is_future attribute. Then you donβt need to do an if-else because the response will always be the same type:
$any_ua->response_is_future(1);
my $resp = $any_ua->get('http://www.perl.org/');
$resp->on_done(sub { # response is always a Future
my $real_resp = shift;
handle_response($real_resp);
});
Note that this doesnβt make a blocking HTTP client magically non-blocking. The call to request will still block if the client is blocking, and your done callback will simply be fired immediately. But this does let you write the same code in your module and have it work regardless of whether the underlying HTTP client is blocking or non-blocking. Thatβs the dream, fr fr.
The default behavior is to return a direct hashref response if the HTTP client is blocking and a Future if the client is non-blocking. Itβs up to you to decide whether or not to set response_is_future, and you should also consider whether you want to expose the possibility of either type of response or always returning Future objects to the end user of your module. It doesnβt matter for users who choose non-blocking HTTP clients because they will be using Future objects either way, but users who know they are using a blocking HTTP client may appreciate not having to deal with Future objects at all.
π ENVIRONMENT π
PERL_HTTP_ANYUA_DEBUGβ If1, print some info useful for debugging toSTDERR.
π CAVEATS π
Okay bestie, real talk: not all HTTP clients implement the same features, and ngl, they do not always do things the same way. The whole point of HTTP::AnyUA is to hide those differences from you, but you might still notice some (hopefully) insignificant little quirks when you plug in different clients. For example, LWP::UserAgent sets some headers on the response such as client-date and client-peer that won't appear when using other clients. Tiny differences like these probably aren't a big deal. π«Ά
Other differences might be a bigger deal, depending on what's important to you. For example, some clients (like HTTP::Tiny) may do chunked transfer encoding in situations where other clients won't (probably because they don't support it). It's not a goal of this project to eliminate all of the differences, but if you come across a difference that is significant enough that you think you need to detect the user agent and write special logic, I would like to learn about your use case. ππ
π SEE ALSO π
These modules share similar goals or provide overlapping functionality:
Future::HTTPHTTP::AnyHTTP::TinyishPlient
Like, go give them a lil vibe check too, iykyk. β¨
π BUGS π
Please report any bugs or feature requests on the bugtracker website <https://github.com/chazmcgarvey/HTTP-AnyUA/issues> β no cap, that's the spot. π«‘
When submitting a bug or request, please include a test-file or a patch to an existing test-file that illustrates the bug or desired feature. That's the good stuff, bestie: receipts, not vibes. π π
π AUTHOR π
Charles McGarvey <chazmcgarvey@brokenzipper.com>
Main character energy fr fr. π
π COPYRIGHT AND LICENSE π
This software is copyright (c) 2019 by Charles McGarvey.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Slay, open source icon. π
And that's the whole page, bestie! π¦ This lil beauty is perl v5.42.2, dated 2023-07-03, and it's giving HTTP::AnyUA(3) β iconic, honestly. π
β¨
π¦ where this page comes from
- alpine 3.24: perl-http-anyua-doc 0.904-r1 Β· GPL-1.0-or-later OR Artistic-1.0-Perl
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms