File Coverage

blib/lib/AnyEvent/HTTP/LWP/UserAgent.pm
Criterion Covered Total %
statement 95 233 40.7
branch 19 86 22.0
condition 10 51 19.6
subroutine 17 31 54.8
pod 15 16 93.7
total 156 417 37.4


line stmt bran cond sub pod time code
1             package AnyEvent::HTTP::LWP::UserAgent;
2             {
3             $AnyEvent::HTTP::LWP::UserAgent::VERSION = '0.10';
4             }
5              
6 7     7   280692 use strict;
  7         18  
  7         636  
7 7     7   38 use warnings;
  7         16  
  7         238  
8              
9             #ABSTRACT: LWP::UserAgent interface but works using AnyEvent::HTTP
10              
11              
12 7     7   11797 use parent qw(LWP::UserAgent);
  7         9732  
  7         47  
13              
14 7     7   622644 use AnyEvent 5; # AE syntax
  7         53434  
  7         358  
15 7     7   8898 use AnyEvent::HTTP 2.1; # http(s)/1.1
  7         346024  
  7         748  
16 7     7   90 use HTTP::Response;
  7         13  
  7         196  
17 7     7   98 use LWP::UserAgent 5.815; # first version with handlers
  7         136  
  7         21917  
18              
19              
20             sub conn_cache {
21 1     1 1 3 my $self = shift;
22              
23 1         11 my $res = $self->SUPER::conn_cache(@_);
24 1         10 my $cache = $self->SUPER::conn_cache;
25 1 50       8 if ($cache) {
26 0         0 my $total_capacity = $cache->total_capacity;
27 0 0       0 $total_capacity = 100_000 unless(defined($total_capacity));
28 0         0 $AnyEvent::HTTP::ACTIVE = $total_capacity;
29             }
30              
31 1         4 return $res;
32             }
33              
34              
35             sub simple_request_async {
36 1     1 1 5 my ($self, $in_req, $arg, $size) = @_;
37              
38 1         7 my ($method, $uri_ref, $args) = $self->lwp_request2anyevent_request($in_req);
39              
40 1         27 my $cv = AE::cv;
41 1         27 my $out_req;
42 1         3 my $content = '';
43 1         1 my $fh;
44 1 50 33     15 if(!ref($arg) && defined($arg) && length($arg)) {
    50 33        
45 0 0       0 open $fh, '>', $arg or $cv->croak("Can't write to '$arg': $!");
46 0         0 binmode $fh;
47             $args->{on_body} = sub {
48 0     0   0 my ($d, $h) = @_;
49 0 0 0     0 if($out_req->code < 200 || 300 <= $out_req->code) { # not success
50 0         0 $content .= $d;
51             } else {
52 0 0       0 print $fh $d or $cv->croak("Can't write to '$arg': $!");
53             }
54 0         0 return 1;
55 0         0 };
56             } elsif(ref($arg) eq 'CODE') {
57             $args->{on_body} = sub {
58 0     0   0 my ($d, $h) = @_;
59 0 0 0     0 if($out_req->code < 200 || 300 <= $out_req->code) { # not success
60 0         0 $content .= $d;
61             } else {
62 0         0 eval { $arg->($d, $out_req, undef) };
  0         0  
63 0         0 my $err = $@;
64 0 0       0 if($err) {
65 0         0 chomp $err;
66 0         0 $out_req->header('X-Died' => $err);
67 0         0 $out_req->header('Client-Aborted' => 'die');
68 0         0 return 0;
69             }
70             }
71 0         0 return 1;
72 0         0 };
73             }
74             my $header_init = sub {
75 1     1   2 my ($d, $h) = @_;
76              
77             # special AnyEvent::HTTP's headers
78 1         3 my $code = delete $h->{Status};
79 1         4 my $message = delete $h->{Reason};
80              
81             # Now we don't use in any place this AnyEvent::HTTP pseudo-headers, so
82             # just delete it
83 1         4 for (qw/HTTPVersion OrigStatus OrigReason Redirect URL/) {
84 5         9 delete $h->{$_};
85             }
86              
87             # AnyEvent::HTTP join headers by comma
88             # in this header exists many times in response.
89             # It is some trie to split such headers, I need
90             # to read RFCs more carefully.
91 1         13 my $headers = HTTP::Headers->new;
92 1         15 while (my ($header, $value) = each %$h) {
93             # In previous versions it was a place where heavily used
94             # Coro stack (if Coro used) when you had pseudo-header URL
95             # and URL was really big.
96             # Now it's not such a big problem, we delete URL pseudo-header
97             # and haven't sudden gigantous headers (I hope).
98 0         0 my @v = $value =~ /^([^ ].*?[^ ],)*([^ ].*?[^ ])$/;
99 0         0 @v = grep { defined($_) } @v;
  0         0  
100 0 0       0 if (scalar(@v) > 1) {
101 0         0 @v = map { s/,$//; $_ } @v;
  0         0  
  0         0  
102 0         0 $value = \@v;
103             }
104 0         0 $headers->header($header => $value);
105             }
106              
107             # special AnyEvent::HTTP codes
108 1 50 33     9 if ($code >= 590 && $code <= 599) {
109             # make LWP-compatible error in the case of timeout
110 1 50 33     15 if ($message =~ /timed/ && $code == 599) {
    50 33        
111 0         0 $d = '500 read timeout';
112 0         0 $code = 500;
113             } elsif (!defined($d) || $d =~ /^\s*$/) {
114 1         3 $d = $message;
115             }
116             }
117 1         11 $out_req = HTTP::Response->new($code, $message, $headers, $d);
118              
119 1         215 $self->run_handlers(response_header => $out_req);
120              
121 1         544 return 1;
122 1         7 };
123             $args->{on_header} = sub {
124 0     0   0 my ($h) = @_;
125 0         0 $header_init->(undef, $h);
126 1         5 };
127              
128             http_request $method => $$uri_ref, %$args, sub {
129 1     1   5389 my ($d, $h) = @_;
130 1 50       15 $d = $content if $content ne '';
131 1 50       192 $header_init->($d, $h) if ! defined $out_req;
132 1 50       5 $out_req->content($d) if defined $d;
133 1 50 0     5 close($fh) or $cv->croak("Can't write to '$arg': $!") if defined ($fh);
134              
135 1 50 33     6 if(defined($d) && length($d)) {
136             # from LWP::Protocol
137 0         0 my %skip_h;
138 0         0 for my $h ($self->handlers('response_data', $out_req)) {
139 0 0       0 next if $skip_h{$h};
140 0 0       0 unless ($h->{callback}->($out_req, $self, $h, $d)) {
141             # XXX remove from $response->{handlers}{response_data} if present
142 0         0 $skip_h{$h}++;
143             }
144             }
145             }
146              
147 1         4 $out_req->request($in_req);
148              
149             # cookie_jar will be set by the handler
150 1         15 $self->run_handlers(response_done => $out_req);
151              
152 1         15 $cv->send($out_req);
153 1         12 };
154              
155 1         25555 return $cv;
156             }
157              
158             sub simple_request {
159 0     0 1 0 return shift->simple_request_async(@_)->recv;
160             }
161              
162             sub get_async {
163 1     1 1 21262 require HTTP::Request::Common;
164 1         8600 my($self, @parameters) = @_;
165 1         24 my @suff = $self->_process_colonic_headers(\@parameters,1);
166 1         20 return $self->request_async( HTTP::Request::Common::GET( @parameters ), @suff );
167             }
168              
169              
170             sub post_async {
171 0     0 1 0 require HTTP::Request::Common;
172 0         0 my($self, @parameters) = @_;
173 0 0       0 my @suff = $self->_process_colonic_headers(\@parameters, (ref($parameters[1]) ? 2 : 1));
174 0         0 return $self->request_async( HTTP::Request::Common::POST( @parameters ), @suff );
175             }
176              
177              
178             sub head_async {
179 0     0 1 0 require HTTP::Request::Common;
180 0         0 my($self, @parameters) = @_;
181 0         0 my @suff = $self->_process_colonic_headers(\@parameters,1);
182 0         0 return $self->request_async( HTTP::Request::Common::HEAD( @parameters ), @suff );
183             }
184              
185              
186             sub put_async {
187 0     0 1 0 require HTTP::Request::Common;
188 0         0 my($self, @parameters) = @_;
189 0 0       0 my @suff = $self->_process_colonic_headers(\@parameters, (ref($parameters[1]) ? 2 : 1));
190 0         0 return $self->request_async( HTTP::Request::Common::PUT( @parameters ), @suff );
191             }
192              
193              
194             sub delete_async {
195 0     0 1 0 require HTTP::Request::Common;
196 0         0 my($self, @parameters) = @_;
197 0         0 my @suff = $self->_process_colonic_headers(\@parameters,1);
198 0         0 return $self->request_async( HTTP::Request::Common::DELETE( @parameters ), @suff );
199             }
200              
201             sub get {
202 1     1 1 3527 return shift->get_async(@_)->recv;
203             }
204              
205             sub post {
206 0     0 1 0 return shift->post_async(@_)->recv;
207             }
208              
209             sub head {
210 0     0 1 0 return shift->head_async(@_)->recv;
211             }
212              
213             sub put {
214 0     0 1 0 return shift->put_async(@_)->recv;
215             }
216              
217             sub delete {
218 0     0 1 0 return shift->delete_async(@_)->recv;
219             }
220              
221             sub lwp_request2anyevent_request {
222 1     1 0 4 my ($self, $in_req) = @_;
223              
224 1         9 my $method = $in_req->method;
225 1         25 my $uri = $in_req->uri->as_string;
226              
227 1 50       189 if ($self->cookie_jar) {
228 0         0 $self->cookie_jar->add_cookie_header($in_req);
229             }
230              
231 1         27 my $in_headers = $in_req->headers;
232 1         10 my $out_headers = {};
233             $in_headers->scan( sub {
234 0     0   0 my ($header, $value) = @_;
235 0         0 $out_headers->{$header} = $value;
236 1         14 } );
237              
238             # if we will use some code like
239             # local $AnyEvent::HTTP::USERAGENT = $useragent;
240             # in simple_request, it will not work properly in redirects
241 1         40 $out_headers->{'User-Agent'} = $self->agent;
242              
243 1         115 my $body;
244 1 50       13 if(ref($in_req->content) eq 'CODE') {
245             # Minimum coderef support
246             # TODO: Add chunked transfer but maybe necessary to modify AnyEvent::HTTP itself
247 0         0 $body = '';
248 0         0 while(my $ret = $in_req->content->()) {
249 0         0 $body .= $ret;
250 0 0       0 last if $ret eq '';
251             }
252             } else {
253 1         17 $body = $in_req->content;
254             }
255              
256 1         17 my %args = (
257             headers => $out_headers,
258             body => $body,
259             recurse => 0, # because LWP call simple_request as much as needed
260             timeout => $self->timeout,
261             );
262 1 50       29 if ($self->conn_cache) {
263 0         0 $args{persistent} = 1;
264 0         0 $args{keepalive} = 1;
265             } else {
266             # By default AnyEvent::HTTP set persistent = 1 for idempotent
267             # requests. So just for compatibility with LWP::UserAgent we
268             # disable this options.
269 1         5 $args{persistent} = 0;
270 1         3 $args{keepalive} = 0;
271             }
272 1         6 return ($method, \$uri, \%args);
273             }
274              
275             sub request_async
276             {
277 1     1 1 17032 my($self, $request, $arg, $size, $previous) = @_;
278              
279 1         281 my $cv = AE::cv;
280             $self->simple_request_async($request, $arg, $size)->cb(sub {
281 1     1   14 my $response = shift->recv;
282 1 50       10 $response->previous($previous) if $previous;
283              
284 1 50       4 if ($response->redirects >= $self->{max_redirect}) {
285 0         0 $response->header("Client-Warning" =>
286             "Redirect loop detected (max_redirect = $self->{max_redirect})");
287 0         0 $cv->send($response); return;
  0         0  
288             }
289              
290 1 50       22 if (my $req = $self->run_handlers("response_redirect", $response)) {
291 0         0 $self->request_async($req, $arg, $size, $response)->cb(sub { $cv->send(shift->recv) }); return;
  0         0  
  0         0  
292             }
293              
294 1         31 my $code = $response->code;
295              
296 1 50 33     48 if ($code == &HTTP::Status::RC_MOVED_PERMANENTLY or
    50 33        
      33        
      33        
297             $code == &HTTP::Status::RC_FOUND or
298             $code == &HTTP::Status::RC_SEE_OTHER or
299             $code == &HTTP::Status::RC_TEMPORARY_REDIRECT)
300             {
301 0         0 my $referral = $request->clone;
302              
303             # These headers should never be forwarded
304 0         0 $referral->remove_header('Host', 'Cookie');
305              
306 0 0 0     0 if ($referral->header('Referer') &&
      0        
307             $request->uri->scheme eq 'https' &&
308             $referral->uri->scheme eq 'http')
309             {
310             # RFC 2616, section 15.1.3.
311             # https -> http redirect, suppressing Referer
312 0         0 $referral->remove_header('Referer');
313             }
314              
315 0 0 0     0 if ($code == &HTTP::Status::RC_SEE_OTHER ||
316             $code == &HTTP::Status::RC_FOUND)
317             {
318 0         0 my $method = uc($referral->method);
319 0 0 0     0 unless ($method eq "GET" || $method eq "HEAD") {
320 0         0 $referral->method("GET");
321 0         0 $referral->content("");
322 0         0 $referral->remove_content_headers;
323             }
324             }
325              
326             # And then we update the URL based on the Location:-header.
327 0         0 my $referral_uri = $response->header('Location');
328             {
329             # Some servers erroneously return a relative URL for redirects,
330             # so make it absolute if it not already is.
331 0         0 local $URI::ABS_ALLOW_RELATIVE_SCHEME = 1;
  0         0  
332 0         0 my $base = $response->base;
333 0 0       0 $referral_uri = "" unless defined $referral_uri;
334 0         0 $referral_uri = $HTTP::URI_CLASS->new($referral_uri, $base)
335             ->abs($base);
336             }
337 0         0 $referral->uri($referral_uri);
338              
339 0 0       0 if($self->redirect_ok($referral, $response)) {
340 0         0 $self->request_async($referral, $arg, $size, $response)->cb(sub{ $cv->send(shift->recv) }); return;
  0         0  
  0         0  
341             } else {
342 0         0 $cv->send($response); return;
  0         0  
343             }
344              
345             }
346             elsif ($code == &HTTP::Status::RC_UNAUTHORIZED ||
347             $code == &HTTP::Status::RC_PROXY_AUTHENTICATION_REQUIRED
348             )
349             {
350 0         0 my $proxy = ($code == &HTTP::Status::RC_PROXY_AUTHENTICATION_REQUIRED);
351 0 0       0 my $ch_header = $proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
352 0         0 my @challenge = $response->header($ch_header);
353 0 0       0 unless (@challenge) {
354 0         0 $response->header("Client-Warning" =>
355             "Missing Authenticate header");
356 0         0 $cv->send($response); return;
  0         0  
357             }
358              
359 0         0 require HTTP::Headers::Util;
360 0         0 CHALLENGE: for my $challenge (@challenge) {
361 0         0 $challenge =~ tr/,/;/; # "," is used to separate auth-params!!
362 0         0 ($challenge) = HTTP::Headers::Util::split_header_words($challenge);
363 0         0 my $scheme = shift(@$challenge);
364 0         0 shift(@$challenge); # no value
365 0         0 $challenge = { @$challenge }; # make rest into a hash
366              
367 0 0       0 unless ($scheme =~ /^([a-z]+(?:-[a-z]+)*)$/) {
368 0         0 $response->header("Client-Warning" =>
369             "Bad authentication scheme '$scheme'");
370 0         0 $cv->send($response); return;
  0         0  
371             }
372 0         0 $scheme = $1; # untainted now
373 0         0 my $class = "LWP::Authen::\u$scheme";
374 0         0 $class =~ s/-/_/g;
375              
376 7     7   81 no strict 'refs';
  7         18  
  7         3513  
377 0 0       0 unless (%{"$class\::"}) {
  0         0  
378             # try to load it
379 0         0 eval "require $class";
380 0 0       0 if ($@) {
381 0 0       0 if ($@ =~ /^Can\'t locate/) {
382 0         0 $response->header("Client-Warning" =>
383             "Unsupported authentication scheme '$scheme'");
384             }
385             else {
386 0         0 $response->header("Client-Warning" => $@);
387             }
388 0         0 next CHALLENGE;
389             }
390             }
391 0 0       0 unless ($class->can("authenticate")) {
392 0         0 $response->header("Client-Warning" =>
393             "Unsupported authentication scheme '$scheme'");
394 0         0 next CHALLENGE;
395             }
396             # TODO: Maybe able to be more asynchronous
397 0         0 $cv->send($class->authenticate($self, $proxy, $challenge, $response,
398 0         0 $request, $arg, $size)); return;
399             }
400 0         0 $cv->send($response); return
401 0         0 }
402 1         8 $cv->send($response); return;
  1         9  
403 1         8863 });
404 1         21 return $cv;
405             }
406              
407             sub request
408             {
409 0     0 1   return shift->request_async(@_)->recv;
410             }
411              
412             1;
413              
414             __END__