File Coverage

blib/lib/AnyEvent/UWSGI.pm
Criterion Covered Total %
statement 29 385 7.5
branch 1 242 0.4
condition 0 76 0.0
subroutine 10 33 30.3
pod 4 12 33.3
total 44 748 5.8


line stmt bran cond sub pod time code
1             =head1 NAME
2            
3             AnyEvent::UWSGI - non-blocking UWSGI client (based on L)
4              
5             =head1 SYNOPSIS
6            
7             use AnyEvent::UWSGI;
8             uwsgi_get "uwsgi://example.com:3044/", headers => {Host => "example.com"}, sub { print $_[0] };
9              
10             =head1 DESCRIPTION
11              
12             This module is an adaptation of AnyEvent::HTTP module for uwsgi protocol.
13             Subroutines prefixed by C (like request, get, post, head) replaced by appropriate subrountines with the prefix C.
14              
15             =head1 METHODS
16              
17             =over 4
18              
19             =cut
20              
21             package AnyEvent::UWSGI;
22              
23 1     1   13457 use common::sense;
  1         7  
  1         4  
24              
25 1     1   454 use Errno ();
  1         835  
  1         30  
26              
27 1     1   847 use AnyEvent 5.0 ();
  1         3803  
  1         21  
28 1     1   490 use AnyEvent::Util ();
  1         7488  
  1         19  
29 1     1   657 use AnyEvent::Handle ();
  1         5176  
  1         19  
30 1     1   504 use AnyEvent::Socket ();
  1         9922  
  1         30  
31              
32 1     1   506 use Encode ();
  1         7002  
  1         18  
33 1     1   400 use URI::Escape::XS ();
  1         1697  
  1         23  
34              
35 1     1   4 use base Exporter::;
  1         1  
  1         5496  
36              
37             our $VERSION = '0.03';
38              
39             our @EXPORT = qw(uwsgi_get uwsgi_post uwsgi_head uwsgi_request);
40              
41             our $USERAGENT = "Mozilla/5.0 (compatible; U; AnyEvent-UWSGI/$VERSION; +http://software.schmorp.de/pkg/AnyEvent.html)";
42             our $MAX_RECURSE = 10;
43             our $PERSISTENT_TIMEOUT = 3;
44             our $TIMEOUT = 300;
45             our $MAX_PER_HOST = 4; # changing this is evil
46              
47             our $PROXY;
48             our $ACTIVE = 0;
49              
50             my %KA_CACHE; # indexed by uhost currently, points to [$handle...] array
51             my %CO_SLOT; # number of open connections, and wait queue, per host
52              
53              
54             #############################################################################
55             # wait queue/slots
56              
57             sub _slot_schedule;
58             sub _slot_schedule($) {
59 0     0   0 my $host = shift;
60              
61 0         0 while ($CO_SLOT{$host}[0] < $MAX_PER_HOST) {
62 0 0       0 if (my $cb = shift @{ $CO_SLOT{$host}[1] }) {
  0         0  
63             # somebody wants that slot
64 0         0 ++$CO_SLOT{$host}[0];
65 0         0 ++$ACTIVE;
66              
67             $cb->(AnyEvent::Util::guard {
68 0     0   0 --$ACTIVE;
69 0         0 --$CO_SLOT{$host}[0];
70 0         0 _slot_schedule $host;
71 0         0 });
72             } else {
73             # nobody wants the slot, maybe we can forget about it
74 0 0       0 delete $CO_SLOT{$host} unless $CO_SLOT{$host}[0];
75 0         0 last;
76             }
77             }
78             }
79              
80             # wait for a free slot on host, call callback
81             sub _get_slot($$) {
82 0     0   0 push @{ $CO_SLOT{$_[0]}[1] }, $_[1];
  0         0  
83              
84 0         0 _slot_schedule $_[0];
85             }
86              
87             #############################################################################
88             # cookie handling
89              
90             # expire cookies
91             sub cookie_jar_expire($;$) {
92 0     0 0 0 my ($jar, $session_end) = @_;
93              
94 0 0       0 %$jar = () if $jar->{version} != 1;
95              
96 0         0 my $anow = AE::now;
97              
98 0         0 while (my ($chost, $paths) = each %$jar) {
99 0 0       0 next unless ref $paths;
100              
101 0         0 while (my ($cpath, $cookies) = each %$paths) {
102 0         0 while (my ($cookie, $kv) = each %$cookies) {
103 0 0       0 if (exists $kv->{_expires}) {
    0          
104             delete $cookies->{$cookie}
105 0 0       0 if $anow > $kv->{_expires};
106             } elsif ($session_end) {
107 0         0 delete $cookies->{$cookie};
108             }
109             }
110              
111 0 0       0 delete $paths->{$cpath}
112             unless %$cookies;
113             }
114              
115 0 0       0 delete $jar->{$chost}
116             unless %$paths;
117             }
118             }
119            
120             # extract cookies from jar
121             sub cookie_jar_extract($$$$) {
122 0     0 0 0 my ($jar, $scheme, $host, $path) = @_;
123              
124 0 0       0 %$jar = () if $jar->{version} != 1;
125              
126 0         0 my @cookies;
127              
128 0         0 while (my ($chost, $paths) = each %$jar) {
129 0 0       0 next unless ref $paths;
130              
131 0 0       0 if ($chost =~ /^\./) {
    0          
132 0 0       0 next unless $chost eq substr $host, -length $chost;
133             } elsif ($chost =~ /\./) {
134 0 0       0 next unless $chost eq $host;
135             } else {
136 0         0 next;
137             }
138              
139 0         0 while (my ($cpath, $cookies) = each %$paths) {
140 0 0       0 next unless $cpath eq substr $path, 0, length $cpath;
141              
142 0         0 while (my ($cookie, $kv) = each %$cookies) {
143 0 0 0     0 if (exists $kv->{_expires} and AE::now > $kv->{_expires}) {
144 0         0 delete $cookies->{$cookie};
145 0         0 next;
146             }
147              
148 0         0 my $value = $kv->{value};
149              
150 0 0       0 if ($value =~ /[=;,[:space:]]/) {
151 0         0 $value =~ s/([\\"])/\\$1/g;
152 0         0 $value = "\"$value\"";
153             }
154              
155 0         0 push @cookies, "$cookie=$value";
156             }
157             }
158             }
159              
160             \@cookies
161 0         0 }
162            
163             # parse set_cookie header into jar
164             sub cookie_jar_set_cookie($$$$) {
165 0     0 0 0 my ($jar, $set_cookie, $host, $date) = @_;
166              
167 0         0 my $anow = int AE::now;
168 0         0 my $snow; # server-now
169              
170 0         0 for ($set_cookie) {
171             # parse NAME=VALUE
172 0         0 my @kv;
173              
174             # expires is not http-compliant in the original cookie-spec,
175             # we support the official date format and some extensions
176 0         0 while (
177             m{
178             \G\s*
179             (?:
180             expires \s*=\s* ([A-Z][a-z][a-z]+,\ [^,;]+)
181             | ([^=;,[:space:]]+) (?: \s*=\s* (?: "((?:[^\\"]+|\\.)*)" | ([^;,[:space:]]*) ) )?
182             )
183             }gcxsi
184             ) {
185 0         0 my $name = $2;
186 0         0 my $value = $4;
187              
188 0 0       0 if (defined $1) {
    0          
189             # expires
190 0         0 $name = "expires";
191 0         0 $value = $1;
192             } elsif (defined $3) {
193             # quoted
194 0         0 $value = $3;
195 0         0 $value =~ s/\\(.)/$1/gs;
196             }
197              
198 0 0       0 push @kv, @kv ? lc $name : $name, $value;
199              
200 0 0       0 last unless /\G\s*;/gc;
201             }
202              
203 0 0       0 last unless @kv;
204              
205 0         0 my $name = shift @kv;
206 0         0 my %kv = (value => shift @kv, @kv);
207              
208 0 0       0 if (exists $kv{"max-age"}) {
    0          
209 0         0 $kv{_expires} = $anow + delete $kv{"max-age"};
210             } elsif (exists $kv{expires}) {
211 0   0     0 $snow ||= parse_date ($date) || $anow;
      0        
212 0         0 $kv{_expires} = $anow + (parse_date (delete $kv{expires}) - $snow);
213             } else {
214 0         0 delete $kv{_expires};
215             }
216              
217 0         0 my $cdom;
218 0   0     0 my $cpath = (delete $kv{path}) || "/";
219              
220 0 0       0 if (exists $kv{domain}) {
221 0         0 $cdom = delete $kv{domain};
222              
223 0         0 $cdom =~ s/^\.?/./; # make sure it starts with a "."
224              
225 0 0       0 next if $cdom =~ /\.$/;
226              
227             # this is not rfc-like and not netscape-like. go figure.
228 0         0 my $ndots = $cdom =~ y/.//;
229 0 0       0 next if $ndots < ($cdom =~ /\.[^.][^.]\.[^.][^.]$/ ? 3 : 2);
    0          
230             } else {
231 0         0 $cdom = $host;
232             }
233              
234             # store it
235 0         0 $jar->{version} = 1;
236 0         0 $jar->{lc $cdom}{$cpath}{$name} = \%kv;
237              
238 0 0       0 redo if /\G\s*,/gc;
239             }
240             }
241              
242             #############################################################################
243             # keepalive/persistent connection cache
244              
245             # fetch a connection from the keepalive cache
246             sub ka_fetch($) {
247 0     0 0 0 my $ka_key = shift;
248              
249 0         0 my $hdl = pop @{ $KA_CACHE{$ka_key} }; # currently we reuse the MOST RECENTLY USED connection
  0         0  
250             delete $KA_CACHE{$ka_key}
251 0 0       0 unless @{ $KA_CACHE{$ka_key} };
  0         0  
252              
253 0         0 $hdl
254             }
255              
256             sub ka_store($$) {
257 0     0 0 0 my ($ka_key, $hdl) = @_;
258              
259 0   0     0 my $kaa = $KA_CACHE{$ka_key} ||= [];
260              
261             my $destroy = sub {
262 0     0   0 my @ka = grep $_ != $hdl, @{ $KA_CACHE{$ka_key} };
  0         0  
263              
264 0         0 $hdl->destroy;
265              
266             @ka
267             ? $KA_CACHE{$ka_key} = \@ka
268 0 0       0 : delete $KA_CACHE{$ka_key};
269 0         0 };
270              
271             # on error etc., destroy
272 0         0 $hdl->on_error ($destroy);
273 0         0 $hdl->on_eof ($destroy);
274 0         0 $hdl->on_read ($destroy);
275 0         0 $hdl->timeout ($PERSISTENT_TIMEOUT);
276              
277 0         0 push @$kaa, $hdl;
278 0         0 shift @$kaa while @$kaa > $MAX_PER_HOST;
279             }
280              
281             #############################################################################
282             # utilities
283              
284             # continue to parse $_ for headers and place them into the arg
285             sub _parse_hdr() {
286 0     0   0 my %hdr;
287              
288             # things seen, not parsed:
289             # p3pP="NON CUR OTPi OUR NOR UNI"
290              
291 0         0 $hdr{lc $1} .= ",$2"
292             while /\G
293             ([^:\000-\037]*):
294             [\011\040]*
295             ((?: [^\012]+ | \012[\011\040] )*)
296             \012
297             /gxc;
298              
299 0 0       0 /\G$/
300             or return;
301              
302             # remove the "," prefix we added to all headers above
303             substr $_, 0, 1, ""
304 0         0 for values %hdr;
305              
306 0         0 \%hdr
307             }
308              
309             #############################################################################
310             our $qr_nlnl = qr{(?
311              
312             # maybe it should just become a normal object :/
313              
314             sub _destroy_state(\%) {
315 0     0   0 my ($state) = @_;
316              
317 0 0       0 $state->{handle}->destroy if $state->{handle};
318 0         0 %$state = ();
319             }
320              
321             sub _error(\%$$) {
322 0     0   0 my ($state, $cb, $hdr) = @_;
323              
324 0         0 &_destroy_state ($state);
325              
326 0         0 $cb->(undef, $hdr);
327             ()
328 0         0 }
329              
330             our %IDEMPOTENT = (
331             DELETE => 1,
332             GET => 1,
333             HEAD => 1,
334             OPTIONS => 1,
335             PUT => 1,
336             TRACE => 1,
337              
338             ACL => 1,
339             "BASELINE-CONTROL" => 1,
340             BIND => 1,
341             CHECKIN => 1,
342             CHECKOUT => 1,
343             COPY => 1,
344             LABEL => 1,
345             LINK => 1,
346             MERGE => 1,
347             MKACTIVITY => 1,
348             MKCALENDAR => 1,
349             MKCOL => 1,
350             MKREDIRECTREF => 1,
351             MKWORKSPACE => 1,
352             MOVE => 1,
353             ORDERPATCH => 1,
354             PROPFIND => 1,
355             PROPPATCH => 1,
356             REBIND => 1,
357             REPORT => 1,
358             SEARCH => 1,
359             UNBIND => 1,
360             UNCHECKOUT => 1,
361             UNLINK => 1,
362             UNLOCK => 1,
363             UPDATE => 1,
364             UPDATEREDIRECTREF => 1,
365             "VERSION-CONTROL" => 1,
366             );
367              
368             =item uwsgi_request
369              
370             Like C
371              
372             =cut
373             sub uwsgi_request($$@) {
374 0     0 1 0 my $cb = pop;
375 0         0 my ($method, $url, %arg) = @_;
376              
377 0         0 my %hdr;
378              
379 0         0 $method = uc $method;
380              
381 0 0       0 if (my $hdr = $arg{headers}) {
382 0         0 while (my ($k, $v) = each %$hdr) {
383 0         0 $hdr{lc $k} = $v;
384             }
385             }
386              
387             # pseudo headers for all subsequent responses
388 0         0 my @pseudo = (URL => $url);
389 0 0       0 push @pseudo, Redirect => delete $arg{Redirect} if exists $arg{Redirect};
390              
391 0 0       0 my $recurse = exists $arg{recurse} ? delete $arg{recurse} : $MAX_RECURSE;
392              
393 0 0       0 return $cb->(undef, { @pseudo, Status => 599, Reason => "Too many redirections" })
394             if $recurse < 0;
395              
396 0 0       0 my $proxy = exists $arg{proxy} ? $arg{proxy} : $PROXY;
397 0   0     0 my $timeout = $arg{timeout} || $TIMEOUT;
398              
399 0         0 my ($uscheme, $uauthority, $upath, $query, undef) = # ignore fragment
400             $url =~ m|^([^:]+):(?://([^/?#]*))?([^?#]*)(?:(\?[^#]*))?(?:#(.*))?$|;
401              
402 0         0 $uscheme = lc $uscheme;
403              
404 0         0 my $uport = 3031;
405              
406 0 0       0 $uauthority =~ /^(?: .*\@ )? ([^\@]+?) (?: : (\d+) )?$/x
407             or return $cb->(undef, { @pseudo, Status => 599, Reason => "Unparsable URL" });
408              
409 0         0 my $uhost = lc $1;
410 0 0       0 $uport = $2 if defined $2;
411              
412             $hdr{host} = defined $2 ? "$uhost:$2" : "$uhost"
413 0 0       0 unless exists $hdr{host};
    0          
414              
415 0         0 $uhost =~ s/^\[(.*)\]$/$1/;
416 0 0       0 $upath .= $query if length $query;
417              
418 0         0 $upath =~ s%^/?%/%;
419              
420             # cookie processing
421 0 0       0 if (my $jar = $arg{cookie_jar}) {
422 0         0 my $cookies = cookie_jar_extract $jar, $uscheme, $uhost, $upath;
423              
424 0 0       0 $hdr{cookie} = join "; ", @$cookies
425             if @$cookies;
426             }
427              
428 0         0 my ($rhost, $rport, $rscheme, $rpath); # request host, port, path
429              
430 0 0       0 if ($proxy) {
431 0         0 ($rpath, $rhost, $rport, $rscheme) = ($url, @$proxy);
432              
433 0 0       0 $rscheme = "uwsgi" unless defined $rscheme;
434 0         0 $rhost = lc $rhost;
435 0         0 $rscheme = lc $rscheme;
436             } else {
437 0         0 ($rhost, $rport, $rscheme, $rpath) = ($uhost, $uport, $uscheme, $upath);
438             }
439              
440             # leave out fragment and query string, just a heuristic
441 0 0       0 $hdr{referer} = "$uscheme://$uauthority$upath" unless exists $hdr{referer};
442 0 0       0 $hdr{"user-agent"} = $USERAGENT unless exists $hdr{"user-agent"};
443              
444             $hdr{"content-length"} = length $arg{body}
445 0 0 0     0 if length $arg{body} || $method ne "GET";
446              
447 0         0 my $idempotent = $IDEMPOTENT{$method};
448              
449             # default value for keepalive is true iff the request is for an idempotent method
450 0 0       0 my $persistent = exists $arg{persistent} ? !!$arg{persistent} : $idempotent;
451 0 0       0 my $keepalive = exists $arg{keepalive} ? !!$arg{keepalive} : !$proxy;
452 0         0 my $was_persistent; # true if this is actually a recycled connection
453              
454             # the key to use in the keepalive cache
455 0         0 my $ka_key = "$uscheme\x00$uhost\x00$uport\x00$arg{sessionid}";
456              
457 0 0       0 $hdr{connection} = ($persistent ? $keepalive ? "keep-alive, " : "" : "close, ") . "Te"; #1.1
    0          
458 0 0       0 $hdr{te} = "trailers" unless exists $hdr{te}; #1.1
459              
460 0         0 my %state = (connect_guard => 1);
461              
462 0         0 my $ae_error = 595; # connecting
463              
464             # handle actual, non-tunneled, request
465             my $handle_actual_request = sub {
466 0     0   0 $ae_error = 596; # request phase
467              
468 0         0 my $hdl = $state{handle};
469 0         0 my ($lport, $lhost) = AnyEvent::Socket::unpack_sockaddr getsockname $hdl->fh;
470              
471              
472 0         0 my $env = {};
473 0 0       0 $env->{QUERY_STRING} = $query =~ m{^\?(.*)$} ? $1 : '';
474 0         0 $env->{REQUEST_METHOD} = $method;
475 0 0       0 $env->{CONTENT_LENGTH} = defined $hdr{"content-length"} ? $hdr{"content-length"} : '';
476 0 0       0 $env->{CONTENT_TYPE} = $method =~ /post/i ? 'application/x-www-form-urlencoded' : '';
477 0         0 $env->{REQUEST_URI} = $rpath;
478 0 0       0 $env->{PATH_INFO} = $rpath =~ m{^([^\?]+)} ? $1 : '';
479 0         0 $env->{SERVER_PROTOCOL}= 'HTTP/1.1';
480 0         0 $env->{REMOTE_ADDR} = AnyEvent::Socket::format_address($lhost);
481 0         0 $env->{REMOTE_PORT} = $lport;
482 0         0 $env->{SERVER_PORT} = $rport;
483 0         0 $env->{SERVER_NAME} = $rhost;
484              
485 0 0       0 if ($hdr{'x-uwsgi-nginx-compatible-mode'}) {
486 0         0 $env->{PATH_INFO} = Encode::decode('utf8', URI::Escape::XS::uri_unescape($env->{PATH_INFO}));
487             }
488              
489 0         0 foreach my $k (keys %hdr) {
490 0         0 (my $env_k = uc $k) =~ tr/-/_/;
491 0 0       0 $env->{"HTTP_$env_k"} = defined $hdr{$k} ? $hdr{$k} : '';
492             }
493              
494 0         0 my $data = '';
495 0         0 foreach my $k (sort keys %$env) {
496 0 0       0 die "Undef value found for $k" unless defined $env->{$k};
497 0         0 $data .= pack 'v/a*v/a*', map { Encode::encode('utf8', $_) } $k, $env->{$k};
  0         0  
498             }
499              
500 0         0 my $req_buf = pack('C1v1C1',
501             5, # PSGI_MODIFIER1,
502             length($data),
503             0, # PSGI_MODIFIER2,
504             ) . $data;
505              
506             # send request
507 0         0 $hdl->push_write($req_buf);
508              
509             # return if error occurred during push_write()
510 0 0       0 return unless %state;
511              
512             # reduce memory usage, save a kitten, also re-use it for the response headers.
513 0         0 %hdr = ();
514              
515             # status line and headers
516             $state{read_response} = sub {
517 0 0       0 return unless %state;
518              
519 0         0 for ("$_[1]") {
520 0         0 y/\015//d; # weed out any \015, as they show up in the weirdest of places.
521 0 0       0 /^HTTP\/0*([0-9\.]+) \s+ ([0-9]{3}) (?: \s+ ([^\012]*) )? \012/gxci
522             or return _error %state, $cb, { @pseudo, Status => 599, Reason => "Invalid server response" };
523              
524             # 100 Continue handling
525             # should not happen as we don't send expect: 100-continue,
526             # but we handle it just in case.
527             # since we send the request body regardless, if we get an error
528             # we are out of-sync, which we currently do NOT handle correctly.
529             return $state{handle}->push_read (line => $qr_nlnl, $state{read_response})
530 0 0       0 if $1 eq 100;
531              
532 0         0 push @pseudo,
533             HTTPVersion => $1,
534             Status => $2,
535             Reason => $3,
536             ;
537              
538 0 0       0 my $hdr = _parse_hdr
539             or return _error %state, $cb, { @pseudo, Status => 599, Reason => "Garbled response headers" };
540              
541 0         0 %hdr = (%$hdr, @pseudo);
542             }
543              
544             # redirect handling
545             # relative uri handling forced by microsoft and other shitheads.
546             # we give our best and fall back to URI if available.
547 0 0       0 if (exists $hdr{location}) {
548 0         0 my $loc = $hdr{location};
549              
550 0 0       0 if ($loc =~ m%^//%) { # //
    0          
    0          
551 0         0 $loc = "$rscheme:$loc";
552              
553             } elsif ($loc eq "") {
554 0         0 $loc = $url;
555              
556             } elsif ($loc !~ /^(?: $ | [^:\/?\#]+ : )/x) { # anything "simple"
557 0         0 $loc =~ s/^\.\/+//;
558              
559 0 0       0 if ($loc !~ m%^[.?#]%) {
    0          
560 0         0 my $prefix = "$rscheme://$uhost:$uport";
561              
562 0 0       0 unless ($loc =~ s/^\///) {
563 0         0 $prefix .= $upath;
564 0         0 $prefix =~ s/\/[^\/]*$//;
565             }
566              
567 0         0 $loc = "$prefix/$loc";
568              
569 0         0 } elsif (eval { require URI }) { # uri
570 0         0 $loc = URI->new_abs ($loc, $url)->as_string;
571              
572             } else {
573 0         0 return _error %state, $cb, { @pseudo, Status => 599, Reason => "Cannot parse Location (URI module missing)" };
574             }
575             }
576              
577 0         0 $hdr{location} = $loc;
578             }
579              
580 0         0 my $redirect;
581              
582 0 0       0 if ($recurse) {
583 0         0 my $status = $hdr{Status};
584              
585             # industry standard is to redirect POST as GET for
586             # 301, 302 and 303, in contrast to HTTP/1.0 and 1.1.
587             # also, the UA should ask the user for 301 and 307 and POST,
588             # industry standard seems to be to simply follow.
589             # we go with the industry standard. 308 is defined
590             # by rfc7538
591 0 0 0     0 if ($status == 301 or $status == 302 or $status == 303) {
    0 0        
      0        
592 0         0 $redirect = 1;
593             # HTTP/1.1 is unclear on how to mutate the method
594 0 0       0 unless ($method eq "HEAD") {
595 0         0 $method = "GET";
596 0         0 delete $arg{body};
597             }
598             } elsif ($status == 307 or $status == 308) {
599 0         0 $redirect = 1;
600             }
601             }
602              
603             my $finish = sub { # ($data, $err_status, $err_reason[, $persistent])
604 0 0       0 if ($state{handle}) {
605             # handle keepalive
606 0 0 0     0 if (
    0 0        
607             $persistent
608             && $_[3]
609             && ($hdr{HTTPVersion} < 1.1
610             ? $hdr{connection} =~ /\bkeep-?alive\b/i
611             : $hdr{connection} !~ /\bclose\b/i)
612             ) {
613 0         0 ka_store $ka_key, delete $state{handle};
614             } else {
615             # no keepalive, destroy the handle
616 0         0 $state{handle}->destroy;
617             }
618             }
619              
620 0         0 %state = ();
621              
622 0 0       0 if (defined $_[1]) {
623 0         0 $hdr{OrigStatus} = $hdr{Status}; $hdr{Status} = $_[1];
  0         0  
624 0         0 $hdr{OrigReason} = $hdr{Reason}; $hdr{Reason} = $_[2];
  0         0  
625             }
626              
627             # set-cookie processing
628 0 0       0 if ($arg{cookie_jar}) {
629 0         0 cookie_jar_set_cookie $arg{cookie_jar}, $hdr{"set-cookie"}, $uhost, $hdr{date};
630             }
631              
632 0 0 0     0 if ($redirect && exists $hdr{location}) {
633             # we ignore any errors, as it is very common to receive
634             # Content-Length != 0 but no actual body
635             # we also access %hdr, as $_[1] might be an erro
636             $state{recurse} =
637             uwsgi_request (
638             $method => $hdr{location},
639             %arg,
640             recurse => $recurse - 1,
641             Redirect => [$_[0], \%hdr],
642             sub {
643 0         0 %state = ();
644 0         0 &$cb
645             },
646 0         0 );
647             } else {
648 0         0 $cb->($_[0], \%hdr);
649             }
650 0         0 };
651              
652 0         0 $ae_error = 597; # body phase
653              
654 0         0 my $chunked = $hdr{"transfer-encoding"} =~ /\bchunked\b/i; # not quite correct...
655              
656 0 0       0 my $len = $chunked ? undef : $hdr{"content-length"};
657              
658             # body handling, many different code paths
659             # - no body expected
660             # - want_body_handle
661             # - te chunked
662             # - 2x length known (with or without on_body)
663             # - 2x length not known (with or without on_body)
664 0 0 0     0 if (!$redirect && $arg{on_header} && !$arg{on_header}(\%hdr)) {
    0 0        
    0 0        
    0 0        
    0 0        
      0        
665 0         0 $finish->(undef, 598 => "Request cancelled by on_header");
666             } elsif (
667             $hdr{Status} =~ /^(?:1..|204|205|304)$/
668             or $method eq "HEAD"
669             or (defined $len && $len == 0) # == 0, not !, because "0 " is true
670             ) {
671             # no body
672 0         0 $finish->("", undef, undef, 1);
673              
674             } elsif (!$redirect && $arg{want_body_handle}) {
675 0         0 $_[0]->on_eof (undef);
676 0         0 $_[0]->on_error (undef);
677 0         0 $_[0]->on_read (undef);
678              
679 0         0 $finish->(delete $state{handle});
680              
681             } elsif ($chunked) {
682 0         0 my $cl = 0;
683 0         0 my $body = "";
684 0   0     0 my $on_body = $arg{on_body} || sub { $body .= shift; 1 };
685              
686             $state{read_chunk} = sub {
687 0 0       0 $_[1] =~ /^([0-9a-fA-F]+)/
688             or return $finish->(undef, $ae_error => "Garbled chunked transfer encoding");
689              
690 0         0 my $len = hex $1;
691              
692 0 0       0 if ($len) {
693 0         0 $cl += $len;
694              
695             $_[0]->push_read (chunk => $len, sub {
696 0 0       0 $on_body->($_[1], \%hdr)
697             or return $finish->(undef, 598 => "Request cancelled by on_body");
698              
699             $_[0]->push_read (line => sub {
700 0 0       0 length $_[1]
701             and return $finish->(undef, $ae_error => "Garbled chunked transfer encoding");
702 0         0 $_[0]->push_read (line => $state{read_chunk});
703 0         0 });
704 0         0 });
705             } else {
706 0   0     0 $hdr{"content-length"} ||= $cl;
707              
708             $_[0]->push_read (line => $qr_nlnl, sub {
709 0 0       0 if (length $_[1]) {
710 0         0 for ("$_[1]") {
711 0         0 y/\015//d; # weed out any \015, as they show up in the weirdest of places.
712              
713 0 0       0 my $hdr = _parse_hdr
714             or return $finish->(undef, $ae_error => "Garbled response trailers");
715              
716 0         0 %hdr = (%hdr, %$hdr);
717             }
718             }
719              
720 0         0 $finish->($body, undef, undef, 1);
721 0         0 });
722             }
723 0         0 };
724              
725 0         0 $_[0]->push_read (line => $state{read_chunk});
726              
727             } elsif ($arg{on_body}) {
728 0 0       0 if (defined $len) {
729             $_[0]->on_read (sub {
730 0         0 $len -= length $_[0]{rbuf};
731              
732 0 0       0 $arg{on_body}(delete $_[0]{rbuf}, \%hdr)
733             or return $finish->(undef, 598 => "Request cancelled by on_body");
734              
735 0 0       0 $len > 0
736             or $finish->("", undef, undef, 1);
737 0         0 });
738             } else {
739             $_[0]->on_eof (sub {
740 0         0 $finish->("");
741 0         0 });
742             $_[0]->on_read (sub {
743 0 0       0 $arg{on_body}(delete $_[0]{rbuf}, \%hdr)
744             or $finish->(undef, 598 => "Request cancelled by on_body");
745 0         0 });
746             }
747             } else {
748 0         0 $_[0]->on_eof (undef);
749              
750 0 0       0 if (defined $len) {
751             $_[0]->on_read (sub {
752             $finish->((substr delete $_[0]{rbuf}, 0, $len, ""), undef, undef, 1)
753 0 0       0 if $len <= length $_[0]{rbuf};
754 0         0 });
755             } else {
756             $_[0]->on_error (sub {
757             ($! == Errno::EPIPE || !$!)
758             ? $finish->(delete $_[0]{rbuf})
759 0 0 0     0 : $finish->(undef, $ae_error => $_[2]);
760 0         0 });
761 0         0 $_[0]->on_read (sub { });
762             }
763             }
764 0         0 };
765              
766             # if keepalive is enabled, then the server closing the connection
767             # before a response can happen legally - we retry on idempotent methods.
768 0 0 0     0 if ($was_persistent && $idempotent) {
769 0         0 my $old_eof = $hdl->{on_eof};
770             $hdl->{on_eof} = sub {
771 0         0 _destroy_state %state;
772              
773 0         0 %state = ();
774             $state{recurse} =
775             uwsgi_request (
776             $method => $url,
777             %arg,
778             recurse => $recurse - 1,
779             persistent => 0,
780             sub {
781 0         0 %state = ();
782 0         0 &$cb
783             }
784 0         0 );
785 0         0 };
786             $hdl->on_read (sub {
787 0 0       0 return unless %state;
788              
789             # as soon as we receive something, a connection close
790             # once more becomes a hard error
791 0         0 $hdl->{on_eof} = $old_eof;
792 0         0 $hdl->push_read (line => $qr_nlnl, $state{read_response});
793 0         0 });
794             } else {
795 0         0 $hdl->push_read (line => $qr_nlnl, $state{read_response});
796             }
797 0         0 };
798              
799             my $prepare_handle = sub {
800 0     0   0 my ($hdl) = $state{handle};
801              
802             $hdl->on_error (sub {
803 0         0 _error %state, $cb, { @pseudo, Status => $ae_error, Reason => $_[2] };
804 0         0 });
805             $hdl->on_eof (sub {
806 0         0 _error %state, $cb, { @pseudo, Status => $ae_error, Reason => "Unexpected end-of-file" };
807 0         0 });
808 0         0 $hdl->timeout_reset;
809 0         0 $hdl->timeout ($timeout);
810 0         0 };
811              
812             # connected to proxy (or origin server)
813             my $connect_cb = sub {
814 0 0   0   0 my $fh = shift
815             or return _error %state, $cb, { @pseudo, Status => $ae_error, Reason => "$!" };
816              
817 0 0       0 return unless delete $state{connect_guard};
818              
819             # get handle
820             $state{handle} = new AnyEvent::Handle
821 0         0 %{ $arg{handle_params} },
  0         0  
822             fh => $fh,
823             peername => $uhost,
824             ;
825              
826 0         0 $prepare_handle->();
827              
828 0 0       0 delete $hdr{"proxy-authorization"} unless $proxy;
829 0         0 $handle_actual_request->();
830 0         0 };
831              
832             _get_slot $uhost, sub {
833 0     0   0 $state{slot_guard} = shift;
834              
835 0 0       0 return unless $state{connect_guard};
836              
837             # try to use an existing keepalive connection, but only if we, ourselves, plan
838             # on a keepalive request (in theory, this should be a separate config option).
839 0 0 0     0 if ($persistent && $KA_CACHE{$ka_key}) {
840 0         0 $was_persistent = 1;
841              
842 0         0 $state{handle} = ka_fetch $ka_key;
843             $state{handle}->destroyed
844 0 0       0 and die "AnyEvent::UWSGI: unexpectedly got a destructed handle (1), please report.";#d#
845 0         0 $prepare_handle->();
846             $state{handle}->destroyed
847 0 0       0 and die "AnyEvent::UWSGI: unexpectedly got a destructed handle (2), please report.";#d#
848 0         0 $handle_actual_request->();
849              
850             } else {
851             my $tcp_connect = $arg{tcp_connect}
852 0   0     0 || do { require AnyEvent::Socket; \&AnyEvent::Socket::tcp_connect };
853              
854 0   0     0 $state{connect_guard} = $tcp_connect->($rhost, $rport, $connect_cb, $arg{on_prepare} || sub { $timeout });
855             }
856 0         0 };
857              
858 0     0   0 defined wantarray && AnyEvent::Util::guard { _destroy_state %state }
859 0 0       0 }
860              
861             =item uwsgi_get
862              
863             Like C
864              
865             =cut
866             sub uwsgi_get($@) {
867 0     0 1 0 unshift @_, "GET";
868 0         0 &uwsgi_request
869             }
870              
871             =item uwsgi_head
872              
873             Like C
874              
875             =cut
876             sub uwsgi_head($@) {
877 0     0 1 0 unshift @_, "HEAD";
878 0         0 &uwsgi_request
879             }
880              
881             =item uwsgi_post
882              
883             Like C
884              
885             =cut
886             sub uwsgi_post($$@) {
887 0     0 1 0 my $url = shift;
888 0         0 unshift @_, "POST", $url, "body";
889 0         0 &uwsgi_request
890             }
891              
892             our @month = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
893             our @weekday = qw(Sun Mon Tue Wed Thu Fri Sat);
894              
895             sub format_date($) {
896 0     0 0 0 my ($time) = @_;
897              
898             # RFC 822/1123 format
899 0         0 my ($S, $M, $H, $mday, $mon, $year, $wday, $yday, undef) = gmtime $time;
900              
901 0         0 sprintf "%s, %02d %s %04d %02d:%02d:%02d GMT",
902             $weekday[$wday], $mday, $month[$mon], $year + 1900,
903             $H, $M, $S;
904             }
905              
906             sub parse_date($) {
907 0     0 0 0 my ($date) = @_;
908              
909 0         0 my ($d, $m, $y, $H, $M, $S);
910              
911 0 0       0 if ($date =~ /^[A-Z][a-z][a-z]+, ([0-9][0-9]?)[\- ]([A-Z][a-z][a-z])[\- ]([0-9][0-9][0-9][0-9]) ([0-9][0-9]?):([0-9][0-9]?):([0-9][0-9]?) GMT$/) {
    0          
    0          
912             # RFC 822/1123, required by RFC 2616 (with " ")
913             # cookie dates (with "-")
914              
915 0         0 ($d, $m, $y, $H, $M, $S) = ($1, $2, $3, $4, $5, $6);
916              
917             } elsif ($date =~ /^[A-Z][a-z][a-z]+, ([0-9][0-9]?)-([A-Z][a-z][a-z])-([0-9][0-9]) ([0-9][0-9]?):([0-9][0-9]?):([0-9][0-9]?) GMT$/) {
918             # RFC 850
919 0 0       0 ($d, $m, $y, $H, $M, $S) = ($1, $2, $3 < 69 ? $3 + 2000 : $3 + 1900, $4, $5, $6);
920              
921             } elsif ($date =~ /^[A-Z][a-z][a-z]+ ([A-Z][a-z][a-z]) ([0-9 ]?[0-9]) ([0-9][0-9]?):([0-9][0-9]?):([0-9][0-9]?) ([0-9][0-9][0-9][0-9])$/) {
922             # ISO C's asctime
923 0         0 ($d, $m, $y, $H, $M, $S) = ($2, $1, $6, $3, $4, $5);
924             }
925             # other formats fail in the loop below
926              
927 0         0 for (0..11) {
928 0 0       0 if ($m eq $month[$_]) {
929 0         0 require Time::Local;
930 0         0 return eval { Time::Local::timegm ($S, $M, $H, $d, $_, $y) };
  0         0  
931             }
932             }
933              
934             undef
935 0         0 }
936              
937             sub set_proxy($) {
938 1 50   1 0 4 if (length $_[0]) {
939 0 0       0 $_[0] =~ m%^(uwsgi):// ([^:/]+) (?: : (\d*) )?%ix
940             or Carp::croak "$_[0]: invalid proxy URL";
941 0   0     0 $PROXY = [$2, $3 || 3128, $1]
942             } else {
943 1         2 undef $PROXY;
944             }
945             }
946              
947             # initialise proxy from environment
948             eval {
949             set_proxy $ENV{http_proxy};
950             };
951              
952             =back
953              
954             =head1 SEE ALSO
955            
956             L
957            
958             =head1 AUTHOR
959              
960             Alexander Kazakov, Evoland.kot@gmail.comE
961              
962             =head1 COPYRIGHT AND LICENSE
963              
964             Copyright (C) 2016 by Alexander Kazakov
965              
966             This library is free software; you can redistribute it and/or modify
967             it under the same terms as Perl itself, either Perl version 5.8.8 or,
968             at your option, any later version of Perl 5 you may have available.
969              
970             =cut
971              
972             1