NAME
UserAgent::Any – Wrapper above any UserAgent library, supporting sync and async calls
SYNOPSIS
my $ua = UserAgent::Any->new(LWP::UserAgent->new(%options));
my $res = $ua->get($url, %params);
DESCRIPTION
UserAgent::Any
is to user agents what Log::Any is to loggers: it allows to write libraries making RPC calls without having to rely on one particular user agent implementation.
UserAgent::Any
supports both synchronous and asynchronous calls (if supported by the underlying user agent).
The main goal of this library is to be used for Cloud API wrappers so that they can be written without imposing the use of one particular user agent on their users. As such, only a subset of the features usually exposed by full-fledged user agents is available for now in UserAgent::Any
. Feel free to ask for or contribute new features if needed.
Supported user agents
LWP::UserAgent
When using an LWP::UserAgent, a UserAgent::Any
object only implements the synchronous calls (without the _cb
or _p
suffixes) and the asynchronous ones will throw exceptions when called.
Mojo::UserAgent
When using a Mojo::UserAgent, a UserAgent::Any
object implements the asynchronous calls using the global singleton Mojo::IOLoop and the methods with the _p
suffix return Mojo::Promise objects.
AnyEvent::UserAgent
When using a AnyEvent::UserAgent, a UserAgent::Any
object implements the asynchronous calls using AnyEvent condvar
and the methods with the _p
suffix return Promise::XS objects (that module needs to be installed).
Note that you probably want to set the event loop used by the promise, which has global effect so is not done by this module. It can be achieved with:
Promise::XS::use_event('AnyEvent');
You can read more about that in "EVENT LOOPS" in Promise::XS.
If you need different promise objects (especially Future), feel free to ask for or contribute new implementations.
HTTP::Promise
When using a HTTP::Promise, a UserAgent::Any
object implements the asynchronous calls using Promise::Me which execute the calls in forked processes. Because of that, there are some caveats with the use of this module and its usage is discouraged when another one can work.
UserAgent::Any
As a convenience, you can pass a UserAgent::Any
to the constructor of the package and the exact same object will be returned.
Constructor
my $ua = UserAgent::Any->new($underlying_ua);
Builds a new UserAgent::Any
object wrapping the given underlying user agent. The wrapped object must be an instance of a supported user agent. Feel free to ask for or contribute new implementations.
Synchronous and asynchronous supports
When supported by the underlying user agent, all the UserAgent::Any
methods exist in 3 versions. The synchronous version, without a suffix, and two asynchronous versions, one taking a callback executed when the call is done and one returning a promise that is fulfilled with the result of the call
Note that, as documented above in supported user agent, the asynchronous methods will throw an exception if the object is built with a user agent that does not support asynchronous calls.
See the documentation of the get()
method for more explanation on the asynchronous support. And see the documentation of UserAgent::Any::Wrapper to learn how you can easily expose sets of methods with the same sync/async semantics in your own library.
User agent methods
get
my $res = $ua->get($url, %params);
$ua->get_cb($url, %params)->($cb);
my $promise = $ua->get_p($url, %params);
Execute an HTTP call with the GET
verb to the given url and with the specified parameters, passed as request headers.
Note that, while the examples above are using %params
, the arguments are actually treated as a list and the same key can appear multiple times to send the same header multiple times. So, that list of arguments must still be an even-sized list of alternating key-value pairs.
Like all the user agent methods below, the synchronous get()
returns a UserAgent::Any::Response object, the asynchronous-with-callback get_cb()
returns a code-reference expecting a callback that will be called with an UserAgent::Any::Response object, and the promise based get_p()
will return a promise, whose type depends on the type of user agent used, and that will be fulfilled with a UserAgent::Any::Response object once the request returns.
Note that the two steps call to get_cb()
is just syntactic sugar and the actual GET call is done after the callback is passed. Nothing will happen if the code reference returned by get_cb()
is not called.
If an error happens (like an invalid argument) these methods will throw an exception synchronously in the initial call. It is unlikely (but possible) that the methods fail during the processing of the request in which case the error handling depends on the underlying user agent asynchronous model. With the callback based methods you can generally not catch the errors while the promise based one can allow it (in general through a catch()
method or a second argument to the then()
method).
post
my $res = $ua->post($url, %params, $content);
$ua->post_cb($url, %params, $content)->($cb);
my $promise = $ua->post_p($url, %params, $content);
This is similar to the get
method, except that the call uses the POST
HTTP verb. Also, in addition to the $url
and %params
(which is still actually a @params
) arguments, this method can take an optional $content
scalar that will be sent as the body of the request.
delete
my $res = $ua->delete($url, %params);
$ua->delete_cb($url, %params)->($cb);
my $promise = $ua->delete_p($url, %params);
Same as the get
method, but uses the DELETE
HTTP verb for the request.
patch
my $res = $ua->patch($url, %params, $content);
$ua->patch_cb($url, %params, $content)->($cb);
my $promise = $ua->patch_p($url, %params, $content);
Same as the post
method, but uses the PATCH
HTTP verb for the request.
put
my $res = $ua->put($url, %params, $content);
$ua->put_cb($url, %params, $content)->($cb);
my $promise = $ua->put_p($url, %params, $content);
Same as the post
method, but uses the PUT
HTTP verb for the request.
head
my $res = $ua->head($url, %params);
$ua->head_cb($url, %params)->($cb);
my $promise = $ua->head_p($url, %params);
Same as the get
method, but uses the HEAD
HTTP verb for the request. Note that it means that in general the user agent will ignore the content returned by the server (except for the headers), even if some content is returned.
BUGS AND LIMITATIONS
AnyEvent::UserAgent does not properly support sending a single header multiple times: all the values will be concatenated (separated by
,
) and sent as a single header. This is supposed to be equivalent but might give a different behavior from other implementations.The message passing system used by HTTP::Promise (internally based on Promise::Me) appears to be unreliable and a program using it might dead-lock unexpectedly. If you only want to send requests in the background without waiting for their result, then this might not be an issue for you.
AUTHOR
Mathias Kende <mathias@cpan.org>
COPYRIGHT AND LICENSE
Copyright 2024 Mathias Kende
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.