]> Dogcows Code - chaz/p5-HTTP-AnyUA/blob - lib/HTTP/AnyUA.pm
add middleware
[chaz/p5-HTTP-AnyUA] / lib / HTTP / AnyUA.pm
1 package HTTP::AnyUA;
2 # ABSTRACT: An HTTP user agent programming interface unification layer
3
4 =head1 SYNOPSIS
5
6 my $any_ua = HTTP::AnyUA->new(ua => LWP::UserAgent->new);
7 # OR: my $any_ua = HTTP::AnyUA->new(ua => Furl->new);
8 # OR: my $any_ua = HTTP::AnyUA->new(ua => HTTP::Tiny->new);
9 # etc...
10
11 my $response = $any_ua->get('http://www.example.com/');
12
13 print "$response->{status} $response->{reason}\n";
14
15 while (my ($k, $v) = each %{$response->{headers}}) {
16 for (ref $v eq 'ARRAY' ? @$v : $v) {
17 print "$k: $_\n";
18 }
19 }
20
21 print $response->{content} if length $response->{content};
22
23 ### Non-blocking user agents cause Future objects to be returned:
24
25 my $any_ua = HTTP::AnyUA->new(ua => HTTP::Tiny->new, response_is_future => 1);
26 # OR: my $any_ua = HTTP::AnyUA->new(ua => 'AnyEvent::HTTP');
27 # OR: my $any_ua = HTTP::AnyUA->new(ua => Mojo::UserAgent->new);
28 # etc...
29
30 my $future = $any_ua->get('http://www.example.com/');
31
32 $future->on_done(sub {
33 my $response = shift;
34
35 print "$response->{status} $response->{reason}\n";
36
37 while (my ($k, $v) = each %{$response->{headers}}) {
38 for (ref $v eq 'ARRAY' ? @$v : $v) {
39 print "$k: $_\n";
40 }
41 }
42
43 print $response->{content} if length $response->{content};
44 });
45
46 $future->on_fail(sub { print STDERR "Oh no!!\n" });
47
48 =head1 DESCRIPTION
49
50 This module provides a small wrapper for unifying the programming interfaces of several different
51 actual user agents (HTTP clients) under one B<familiar> interface.
52
53 Rather than providing yet another programming interface for you to learn, HTTP::AnyUA follows the
54 L<HTTP::Tiny> interface. This also means that you can plug in any supported HTTP client
55 (L<LWP::UserAgent>, L<Furl>, etc.) and use it as if it were L<HTTP::Tiny>.
56
57 There are a lot of great HTTP clients available for Perl, each with different goals, different
58 feature sets, and of course different programming interfaces! If you're an end user, you can just
59 pick one of these clients according to the needs of your project (or personal preference). But if
60 you're writing a module that needs to interface with a web server (like perhaps a RESTful API
61 wrapper) and you want your users to be able to use whatever HTTP client they want, HTTP::AnyUA can
62 help you support that!
63
64 It's a good idea to let the end user pick whatever HTTP client they want to use, because they're the
65 one who knows the requirements of their application or script. If you're writing an event-driven
66 application, you'll need to use a non-blocking user agent like L<Mojo::UserAgent>. If you're writing
67 a simple command-line script, you may decide that your priority is to minimize dependencies and so
68 may want to go with L<HTTP::Tiny>.
69
70 Unfortunately, many modules on CPAN are hardcoded to work with specific HTTP clients, leaving the
71 end user unable to use the HTTP client that would be best for them. Although the end user won't --
72 or at least doesn't need to -- use HTTP::AnyUA directly, they will benefit from client choice if
73 their third-party modules use HTTP::AnyUA or something like it.
74
75 The primary goal of HTTP::AnyUA is to make it easy for module developers to write HTTP code once
76 that can work with any HTTP client the end user may decide to plug in. A secondary goal is to make
77 it easy for anyone to add support for new or yet-unsupported user agents.
78
79 =head1 SPECIFICATION
80
81 This section specifies a standard set of data structures that can be used to make a request and get
82 a response from a user agent. This is the specification HTTP::AnyUA uses for its programming
83 interface. It is heavily based on L<HTTP::Tiny>'s interface, and parts of this specification were
84 adapted or copied verbatim from that module's documentation. The intent is for this specification to
85 be written such that L<HTTP::Tiny> is already a compliant implementor of the specification (at least
86 as of the specification's publication date).
87
88 =head2 The Request
89
90 A request is a tuple of the form C<(Method, URL)> or C<(Method, URL, Options)>.
91
92 =head3 Method
93
94 Method B<MUST> be a string representing the HTTP verb. This is commonly C<"GET">, C<"POST">,
95 C<"HEAD">, C<"DELETE">, etc.
96
97 =head3 URL
98
99 URL B<MUST> be a string representing the remote resource to be acted upon. The URL B<MUST> have
100 unsafe characters escaped and international domain names encoded before being passed to the user
101 agent. A user agent B<MUST> generate a C<"Host"> header based on the URL in accordance with RFC
102 2616; a user agent B<MAY> throw an error if a C<"Host"> header is given with the L</headers>.
103
104 =head3 Options
105
106 Options, if present, B<MUST> be a hash reference containing zero or more of the following keys with
107 appropriate values. A user agent B<MAY> support more options than are specified here.
108
109 =head4 headers
110
111 The value for the C<headers> key B<MUST> be a hash reference containing zero or more HTTP header
112 names (as keys) and header values. The value for a header B<MUST> be either a string containing the
113 header value OR an array reference where each item is a string. If the value for a header is an
114 array reference, the user agent B<MUST> output the header multiple times with each value in the
115 array.
116
117 User agents B<MAY> may add headers, but B<SHOULD NOT> replace user-specified headers unless
118 otherwise documented.
119
120 =head4 content
121
122 The value for the C<content> key B<MUST> be a string OR a code reference. If the value is a string,
123 its contents will be included with the request as the body. If the value is a code reference, the
124 referenced code will be called iteratively to produce the body of the request, and the code B<MUST>
125 return an empty string or undef value to indicate the end of the request body. If the value is
126 a code reference, a user agent B<SHOULD> use chunked transfer encoding if it supports it, otherwise
127 a user agent B<MAY> completely drain the code of content before sending the request.
128
129 =head4 data_callback
130
131 The value for the C<data_callback> key B<MUST> be a code reference that will be called zero or more
132 times, once for each "chunk" of response body received. A user agent B<MAY> send the entire response
133 body in one call. The referenced code B<MUST> be given two arguments; the first is a string
134 containing a chunk of the response body, the second is an in-progress L<response|/The Response>.
135
136 =head2 The Response
137
138 A response B<MUST> be a hash reference containg some required keys and values. A response B<MAY>
139 contain some optional keys and values.
140
141 =head3 success
142
143 A response B<MUST> include a C<success> key, the value of which is a boolean indicating whether or
144 not the request is to be considered a success (true is a success). Unless otherwise documented,
145 a successful result means that the operation returned a 2XX status code.
146
147 =head3 url
148
149 A response B<MUST> include a C<url> key, the value of which is the URL that provided the response.
150 This is the URL used in the request unless there were redirections, in which case it is the last URL
151 queried in a redirection chain.
152
153 =head3 status
154
155 A response B<MUST> include a C<status> key, the value of which is the HTTP status code of the
156 response. If an internal exception occurs (e.g. connection error), then the status code B<MUST> be
157 C<599>.
158
159 =head3 reason
160
161 A response B<MUST> include a C<reason> key, the value of which is the response phrase returned by
162 the server OR "Internal Exception" if an internal exception occurred.
163
164 =head3 content
165
166 A response B<MAY> include a C<content> key, the value of which is the response body returned by the
167 server OR the text of the exception if an internal exception occurred. This field B<MUST> be missing
168 or empty if the server provided no response OR if the body was already provided via
169 L</data_callback>.
170
171 =head3 headers
172
173 A response B<SHOULD> include a C<headers> key, the value of which is a hash reference containing
174 zero or more HTTP header names (as keys) and header values. Keys B<MUST> be lowercased. The value
175 for a header B<MUST> be either a string containing the header value OR an array reference where each
176 item is the value of one of the repeated headers.
177
178 =head3 redirects
179
180 A response B<MAY> include a C<redirects> key, the value of which is an array reference of one or
181 more responses from redirections that occurred to fulfill the current request, in chronological
182 order.
183
184 =head1 FREQUENTLY ASKED QUESTIONS
185
186 =head2 How do I set up proxying, SSL, cookies, timeout, etc.?
187
188 HTTP::AnyUA provides a common interface for I<using> HTTP clients, not for instantiating or
189 configuring them. Proxying, SSL, and other custom settings can be configured directly through the
190 underlying HTTP client; see the documentation for your particular user agent to learn how to
191 configure these things.
192
193 L<AnyEvent::HTTP> is a bit of a special case because there is no instantiated object representing
194 the client. For this particular user agent, you can configure the backend to pass a default set of
195 options whenever it calls C<http_request>. See L<HTTP::AnyUA::Backend::AnyEvent::HTTP/options>:
196
197 $any_ua->backend->options({recurse => 5, timeout => 15});
198
199 If you are a module writer, you should probably receive a user agent from your end user and leave
200 this type of configuration up to them.
201
202 =head2 Why use HTTP::AnyUA instead of some other HTTP client?
203
204 Maybe you shouldn't. If you're an end user writing a script or application, you can just pick the
205 HTTP client that suits you best and use it. For example, if you're writing a L<Mojolicious> app,
206 you're not going wrong by using L<Mojo::UserAgent>; it's loaded with features and is well-integrated
207 with that particular environment.
208
209 As an end user, you I<could> wrap the HTTP client you pick in an HTTP::AnyUA object, but the only
210 reason to do this is if you prefer using the L<HTTP::Tiny> interface.
211
212 The real benefit of HTTP::AnyUA (or something like it) is if module writers use it to allow end
213 users of their modules to be able to plug in whatever HTTP client they want. For example, a module
214 that implements an API wrapper that has a hard dependency on L<LWP::UserAgent> or even L<HTTP::Tiny>
215 is essentially useless for non-blocking applications. If the same hypothetical module had been
216 written using HTTP::AnyUA then it would be useful in any scenario.
217
218 =head2 Why use the HTTP::Tiny interface?
219
220 The L<HTTP::Tiny> interface is simple but provides all the essential functionality needed for
221 a capable HTTP client and little more. That makes it easy to provide an implementation for, and it
222 also makes it straightforward for module authors to use.
223
224 Marrying the L<HTTP::Tiny> interface with L<Future> gives us these benefits for both blocking and
225 non-blocking modules and applications.
226
227 =head1 SUPPORTED USER AGENTS
228
229 =for :list
230 * L<AnyEvent::HTTP>
231 * L<Furl>
232 * L<HTTP::AnyUA> - a little bit meta, but why not?
233 * L<HTTP::Tiny>
234 * L<LWP::UserAgent>
235 * L<Mojo::UserAgent>
236 * L<Net::Curl::Easy>
237
238 Any HTTP client that inherits from one of these in a well-behaved manner should also be supported.
239
240 Of course, there are many other HTTP clients on CPAN that HTTP::AnyUA doesn't yet support. I'm more
241 than happy to help add support for others, so send me a message if you know of an HTTP client that
242 needs support. See L<HTTP::AnyUA::Backend> for how to write support for a new HTTP client.
243
244 =head1 NON-BLOCKING USER AGENTS
245
246 HTTP::AnyUA tries to target the L<HTTP::Tiny> interface, which is a blocking interface. This means
247 that when you call L</request>, it is supposed to not return until either the response is received
248 or an error occurs. This doesn't jive well with non-blocking HTTP clients which expect the flow to
249 reenter an event loop so that the request can complete concurrently.
250
251 In order to reconcile this, a L<Future> will be returned instead of the normal hashref response if
252 the wrapped HTTP client is non-blocking (such as L<Mojo::UserAgent> or L<AnyEvent::HTTP>). This
253 L<Future> object may be used to set up callbacks that will be called when the request is completed.
254 You can call L</response_is_future> to know if the response is or will be a L<Future>.
255
256 This is typically okay for the end user; since they're the one who chose which HTTP client to use in
257 the first place, they should know whether they should expect a L<Future> or a direct response when
258 they make an HTTP request, but it does add some burden on you as a module writer because if you ever
259 need to examine the response, you may need to write code like this:
260
261 my $resp = $any_ua->get('http://www.perl.org/');
262
263 if ($any_ua->response_is_future) {
264 $resp->on_done(sub {
265 my $real_resp = shift;
266 handle_response($real_resp);
267 });
268 }
269 else {
270 handle_response($resp); # response is the real response already
271 }
272
273 This actually isn't too annoying to deal with in practice, but you can avoid it if you like by
274 forcing the response to always be a L<Future>. Just set the L</response_is_future> attribute. Then
275 you don't need to do an if-else because the response will always be the same type:
276
277 $any_ua->response_is_future(1);
278
279 my $resp = $any_ua->get('http://www.perl.org/');
280
281 $resp->on_done(sub { # response is always a Future
282 my $real_resp = shift;
283 handle_response($real_resp);
284 });
285
286 Note that this doesn't make a blocking HTTP client magically non-blocking. The call to L</request>
287 will still block if the client is blocking, and your "done" callback will simply be fired
288 immediately. But this does let you write the same code in your module and have it work regardless of
289 whether the underlying HTTP client is blocking or non-blocking.
290
291 The default behavior is to return a direct hashref response if the HTTP client is blocking and
292 a L<Future> if the client is non-blocking. It's up to you to decide whether or not to set
293 C<response_is_future>, and you should also consider whether you want to expose the possibility of
294 either type of response or always returning L<Future> objects to the end user of your module. It
295 doesn't matter for users who choose non-blocking HTTP clients because they will be using L<Future>
296 objects either way, but users who know they are using a blocking HTTP client may appreciate not
297 having to deal with L<Future> objects at all.
298
299 =head1 ENVIRONMENT
300
301 =for :list
302 * C<PERL_HTTP_ANYUA_DEBUG> - If 1, print some info useful for debugging to C<STDERR>.
303
304 =head1 CAVEATS
305
306 Not all HTTP clients implement the same features or in the same ways. While the point of HTTP::AnyUA
307 is to hide those differences, you may notice some (hopefully) I<insignificant> differences when
308 plugging in different clients. For example, L<LWP::UserAgent> sets some headers on the response such
309 as C<client-date> and C<client-peer> that won't appear when using other clients. Little differences
310 like these probably aren't a big deal. Other differences may be a bigger deal, depending on what's
311 important to you. For example, some clients (like L<HTTP::Tiny>) may do chunked transfer encoding in
312 situations where other clients won't (probably because they don't support it). It's not a goal of
313 this project to eliminate I<all> of the differences, but if you come across a difference that is
314 significant enough that you think you need to detect the user agent and write special logic, I would
315 like to learn about your use case.
316
317 =head1 SEE ALSO
318
319 These modules share similar goals or provide overlapping functionality:
320
321 =for :list
322 * L<Future::HTTP>
323 * L<HTTP::Any>
324 * L<HTTP::Tinyish>
325 * L<Plient>
326
327 =cut
328
329 use 5.010;
330 use warnings;
331 use strict;
332
333 our $VERSION = '9999.999'; # VERSION
334
335 use HTTP::AnyUA::Util;
336 use Module::Loader;
337 use Scalar::Util;
338
339
340 our $BACKEND_NAMESPACE;
341 our $MIDDLEWARE_NAMESPACE;
342 our @BACKENDS;
343 our %REGISTERED_BACKENDS;
344
345 BEGIN {
346 $BACKEND_NAMESPACE = __PACKAGE__ . '::Backend';
347 $MIDDLEWARE_NAMESPACE = __PACKAGE__ . '::Middleware';
348 }
349
350
351 sub _debug_log { print STDERR join(' ', @_), "\n" if $ENV{PERL_HTTP_ANYUA_DEBUG} }
352
353 sub _croak { require Carp; Carp::croak(@_) }
354 sub _usage { _croak("Usage: @_\n") }
355
356
357 =method new
358
359 $any_ua = HTTP::AnyUA->new(ua => $user_agent, %attr);
360 $any_ua = HTTP::AnyUA->new($user_agent, %attr);
361
362 Construct a new HTTP::AnyUA.
363
364 =cut
365
366 sub new {
367 my $class = shift;
368 unshift @_, 'ua' if @_ % 2;
369 my %args = @_;
370 $args{ua} or _usage(q{HTTP::AnyUA->new(ua => $user_agent, %attr)});
371
372 my $self;
373 my @attr = qw(ua backend response_is_future);
374
375 for my $attr (@attr) {
376 $self->{$attr} = $args{$attr} if defined $args{$attr};
377 }
378
379 bless $self, $class;
380
381 $self->_debug_log('Created with user agent', $self->ua);
382
383 # call accessors to get the checks to run
384 $self->ua;
385 $self->response_is_future($args{response_is_future}) if defined $args{response_is_future};
386
387 return $self;
388 }
389
390 =attr ua
391
392 Get the user agent that was passed to L</new>.
393
394 =cut
395
396 sub ua { shift->{ua} or _croak 'User agent is required' }
397
398 =attr response_is_future
399
400 Get and set whether or not responses are L<Future> objects.
401
402 =cut
403
404 sub response_is_future {
405 my $self = shift;
406 my $val = shift;
407
408 if (defined $val) {
409 $self->_debug_log('Set response_is_future to', $val ? 'ON' : 'OFF');
410
411 $self->_check_response_is_future($val);
412 $self->{response_is_future} = $val;
413
414 $self->_module_loader->load('Future') if $self->{response_is_future};
415 }
416 elsif (!defined $self->{response_is_future} && $self->{backend}) {
417 $self->{response_is_future} = $self->backend->response_is_future;
418
419 $self->_module_loader->load('Future') if $self->{response_is_future};
420 }
421
422 return $self->{response_is_future} || '';
423 }
424
425 =attr backend
426
427 Get the backend instance. You normally shouldn't need this.
428
429 =cut
430
431 sub backend {
432 my $self = shift;
433
434 return $self->{backend} if defined $self->{backend};
435
436 $self->{backend} = $self->_build_backend;
437 $self->_check_response_is_future($self->response_is_future);
438
439 return $self->{backend};
440 }
441
442 =method request
443
444 $response = $any_ua->request($method, $url);
445 $response = $any_ua->request($method, $url, \%options);
446
447 Make a L<request|/"The Request">, get a L<response|/"The Response">.
448
449 Compare to L<HTTP::Tiny/request>.
450
451 =cut
452
453 sub request {
454 my ($self, $method, $url, $args) = @_;
455 $args ||= {};
456 @_ == 3 || (@_ == 4 && ref $args eq 'HASH')
457 or _usage(q{$any_ua->request($method, $url, \%options)});
458
459 my $resp = eval { $self->backend->request(uc($method) => $url, $args) };
460 if (my $err = $@) {
461 return $self->_wrap_internal_exception($err);
462 }
463
464 return $self->_wrap_response($resp);
465 }
466
467 =method get, head, put, post, delete
468
469 $response = $any_ua->get($url);
470 $response = $any_ua->get($url, \%options);
471 $response = $any_ua->head($url);
472 $response = $any_ua->head($url, \%options);
473 # etc.
474
475 Shortcuts for L</request> where the method is the method name rather than the first argument.
476
477 Compare to L<HTTP::Tiny/getE<verbar>headE<verbar>putE<verbar>postE<verbar>delete>.
478
479 =cut
480
481 # adapted from HTTP/Tiny.pm
482 for my $sub_name (qw{get head put post delete}) {
483 my %swap = (SUBNAME => $sub_name, METHOD => uc($sub_name));
484 my $code = q[
485 sub {{SUBNAME}} {
486 my ($self, $url, $args) = @_;
487 @_ == 2 || (@_ == 3 && ref $args eq 'HASH')
488 or _usage(q{$any_ua->{{SUBNAME}}($url, \%options)});
489 return $self->request('{{METHOD}}', $url, $args);
490 }
491 ];
492 $code =~ s/\{\{([A-Z_]+)\}\}/$swap{$1}/ge;
493 eval $code; ## no critic
494 }
495
496 =method post_form
497
498 $response = $any_ua->post_form($url, $formdata);
499 $response = $any_ua->post_form($url, $formdata, \%options);
500
501 Does a C<POST> request with the form data encoded and sets the C<Content-Type> header to
502 C<application/x-www-form-urlencoded>.
503
504 Compare to L<HTTP::Tiny/post_form>.
505
506 =cut
507
508 # adapted from HTTP/Tiny.pm
509 sub post_form {
510 my ($self, $url, $data, $args) = @_;
511 (@_ == 3 || @_ == 4 && ref $args eq 'HASH')
512 or _usage(q{$any_ua->post_form($url, $formdata, \%options)});
513
514 my $headers = HTTP::AnyUA::Util::normalize_headers($args->{headers});
515 delete $args->{headers};
516
517 return $self->request(POST => $url, {
518 %$args,
519 content => HTTP::AnyUA::Util::www_form_urlencode($data),
520 headers => {
521 %$headers,
522 'content-type' => 'application/x-www-form-urlencoded',
523 },
524 });
525 }
526
527 =method mirror
528
529 $response = $http->mirror($url, $filepath, \%options);
530 if ($response->{success}) {
531 print "$filepath is up to date\n";
532 }
533
534 Does a C<GET> request and saves the downloaded document to a file. If the file already exists, its
535 timestamp will be sent using the C<If-Modified-Since> request header (which you can override). If
536 the server responds with a C<304> (Not Modified) status, the C<success> field will be true; this is
537 usually only the case for C<2XX> statuses. If the server responds with a C<Last-Modified> header,
538 the file will be updated to have the same modification timestamp.
539
540 Compare to L<HTTP::Tiny/mirror>. This version differs slightly in that this returns internal
541 exception responses (for cases like being unable to write the file locally, etc.) rather than
542 actually throwing the exceptions. The reason for this is that exceptions as responses are easier to
543 deal with for non-blocking HTTP clients, and the fact that this method throws exceptions in
544 L<HTTP::Tiny> seems like an inconsistency in its interface.
545
546 =cut
547
548 # adapted from HTTP/Tiny.pm
549 sub mirror {
550 my ($self, $url, $file, $args) = @_;
551 @_ == 3 || (@_ == 4 && ref $args eq 'HASH')
552 or _usage(q{$any_ua->mirror($url, $filepath, \%options)});
553
554 $args->{headers} = HTTP::AnyUA::Util::normalize_headers($args->{headers});
555
556 if (-e $file and my $mtime = (stat($file))[9]) {
557 $args->{headers}{'if-modified-since'} ||= HTTP::AnyUA::Util::http_date($mtime);
558 }
559 my $tempfile = $file . int(rand(2**31));
560
561 # set up the response body to be written to the file
562 require Fcntl;
563 sysopen(my $fh, $tempfile, Fcntl::O_CREAT()|Fcntl::O_EXCL()|Fcntl::O_WRONLY())
564 or return $self->_wrap_internal_exception(qq/Error: Could not create temporary file $tempfile for downloading: $!\n/);
565 binmode $fh;
566 $args->{data_callback} = sub { print $fh $_[0] };
567
568 my $resp = $self->request(GET => $url, $args);
569
570 my $finish = sub {
571 my $resp = shift;
572
573 close $fh
574 or return HTTP::AnyUA::Util::internal_exception(qq/Error: Caught error closing temporary file $tempfile: $!\n/);
575
576 if ($resp->{success}) {
577 rename($tempfile, $file)
578 or return HTTP::AnyUA::Util::internal_exception(qq/Error replacing $file with $tempfile: $!\n/);
579 my $lm = $resp->{headers}{'last-modified'};
580 if ($lm and my $mtime = HTTP::AnyUA::Util::parse_http_date($lm)) {
581 utime($mtime, $mtime, $file);
582 }
583 }
584 unlink($tempfile);
585
586 $resp->{success} ||= $resp->{status} eq '304';
587
588 return $resp;
589 };
590
591 if ($self->response_is_future) {
592 return $resp->followed_by(sub {
593 my $future = shift;
594 my @resp = $future->is_done ? $future->get : $future->failure;
595 my $resp = $finish->(@resp);
596 if ($resp->{success}) {
597 return Future->done(@resp);
598 }
599 else {
600 return Future->fail(@resp);
601 }
602 });
603 }
604 else {
605 return $finish->($resp);
606 }
607 }
608
609 =method apply_middleware
610
611 $any_ua->apply_middleware($middleware_package);
612 $any_ua->apply_middleware($middleware_package, %args);
613 $any_ua->apply_middleware($middleware_obj);
614
615 Wrap the backend with some new middleware. Middleware packages are relative to the
616 C<HTTP::AnyUA::Middleware::> namespace unless prefixed with a C<+>.
617
618 This effectively replaces the L</backend> with a new object that wraps the previous backend.
619
620 This can be used multiple times to add multiple layers of middleware, and order matters. The last
621 middleware applied is the first one to see the request and last one to get the response. For
622 example, if you apply middleware that does logging and middleware that does caching (and
623 short-circuits on a cache hit), applying your logging middleware I<first> will cause only cache
624 misses to be logged whereas applying your cache middleware first will allow all requests to be
625 logged.
626
627 See L<HTTP::AnyUA::Middleware> for more information about what middleware is and how to write your
628 own middleware.
629
630 =cut
631
632 sub apply_middleware {
633 my $self = shift;
634 my $class = shift;
635
636 if (!ref $class) {
637 $class = "${MIDDLEWARE_NAMESPACE}::${class}" unless $class =~ s/^\+//;
638 $self->_module_loader->load($class);
639 }
640
641 $self->{backend} = $class->wrap($self->backend, @_);
642 $self->_check_response_is_future($self->response_is_future);
643
644 return $self;
645 }
646
647 =method register_backend
648
649 HTTP::AnyUA->register_backend($user_agent_package => $backend_package);
650 HTTP::AnyUA->register_backend('MyAgent' => 'MyBackend'); # HTTP::AnyUA::Backend::MyBackend
651 HTTP::AnyUA->register_backend('LWP::UserAgent' => '+SpecialBackend'); # SpecialBackend
652
653 Register a backend for a new user agent type or override a default backend. Backend packages are
654 relative to the C<HTTP::AnyUA::Backend::> namespace unless prefixed with a C<+>.
655
656 If you only need to set a backend as a one-off thing, you could also pass an instantiated backend to
657 L</new>.
658
659 =cut
660
661 sub register_backend {
662 my ($class, $ua_type, $backend_class) = @_;
663 @_ == 3 or _usage(q{HTTP::AnyUA->register_backend($ua_type, $backend_package)});
664
665 if ($backend_class) {
666 $backend_class = "${BACKEND_NAMESPACE}::${backend_class}" unless $backend_class =~ s/^\+//;
667 $REGISTERED_BACKENDS{$ua_type} = $backend_class;
668 }
669 else {
670 delete $REGISTERED_BACKENDS{$ua_type};
671 }
672 }
673
674
675 # turn a response into a Future if it needs to be
676 sub _wrap_response {
677 my $self = shift;
678 my $resp = shift;
679
680 if ($self->response_is_future && !$self->backend->response_is_future) {
681 # wrap the response in a Future
682 if ($resp->{success}) {
683 $self->_debug_log('Wrapped successful response in a Future');
684 $resp = Future->done($resp);
685 }
686 else {
687 $self->_debug_log('Wrapped failed response in a Future');
688 $resp = Future->fail($resp);
689 }
690 }
691
692 return $resp;
693 }
694
695 sub _wrap_internal_exception { shift->_wrap_response(HTTP::AnyUA::Util::internal_exception(@_)) }
696
697 # get a module loader object
698 sub _module_loader { shift->{_module_loader} ||= Module::Loader->new }
699
700 # get a list of potential backends that may be able to handle the user agent
701 sub _build_backend {
702 my $self = shift;
703 my $ua = shift || $self->ua or _croak 'User agent is required';
704
705 my $ua_type = Scalar::Util::blessed($ua);
706
707 my @classes;
708
709 if ($ua_type) {
710 push @classes, $REGISTERED_BACKENDS{$ua_type} if $REGISTERED_BACKENDS{$ua_type};
711
712 push @classes, "${BACKEND_NAMESPACE}::${ua_type}";
713
714 if (!@BACKENDS) {
715 # search for some backends to try
716 @BACKENDS = sort $self->_module_loader->find_modules($BACKEND_NAMESPACE);
717 $self->_debug_log('Found backends to try (' . join(', ', @BACKENDS) . ')');
718 }
719
720 for my $backend_type (@BACKENDS) {
721 my $plugin = $backend_type;
722 $plugin =~ s/^\Q${BACKEND_NAMESPACE}\E:://;
723 push @classes, $backend_type if $ua->isa($plugin);
724 }
725 }
726 else {
727 push @classes, $REGISTERED_BACKENDS{$ua} if $REGISTERED_BACKENDS{$ua};
728 push @classes, "${BACKEND_NAMESPACE}::${ua}";
729 }
730
731 for my $class (@classes) {
732 if (eval { $self->_module_loader->load($class); 1 }) {
733 $self->_debug_log("Found usable backend (${class})");
734 return $class->new($self->ua);
735 }
736 else {
737 $self->_debug_log($@);
738 }
739 }
740
741 _croak 'Cannot find a usable backend that supports the given user agent';
742 }
743
744 # make sure the response_is_future setting is compatible with the backend
745 sub _check_response_is_future {
746 my $self = shift;
747 my $val = shift;
748
749 # make sure the user agent is not non-blocking
750 if (!$val && $self->{backend} && $self->backend->response_is_future) {
751 _croak 'Cannot disable response_is_future with a non-blocking user agent';
752 }
753 }
754
755 1;
This page took 0.088839 seconds and 4 git commands to generate.