File Coverage

blib/lib/AnyEvent/UWSGI.pm
Criterion Covered Total %
statement 29 385 7.5
branch 1 246 0.4
condition 0 76 0.0
subroutine 10 33 30.3
pod 4 12 33.3
total 44 752 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   67387 use common::sense;
  1         11  
  1         3  
24              
25 1     1   401 use Errno ();
  1         1019  
  1         21  
26              
27 1     1   716 use AnyEvent 5.0 ();
  1         3974  
  1         22  
28 1     1   431 use AnyEvent::Util ();
  1         7654  
  1         22  
29 1     1   687 use AnyEvent::Handle ();
  1         5790  
  1         20  
30 1     1   490 use AnyEvent::Socket ();
  1         12359  
  1         43  
31              
32 1     1   726 use Encode ();
  1         7893  
  1         22  
33 1     1   351 use URI::Escape::XS ();
  1         1955  
  1         36  
34              
35 1     1   7 use base Exporter::;
  1         3  
  1         5786  
36              
37             our $VERSION = '0.04';
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             Also accepts C and C in C<%args>
372              
373             =cut
374             sub uwsgi_request($$@) {
375 0     0 1 0 my $cb = pop;
376 0         0 my ($method, $url, %arg) = @_;
377              
378 0         0 my %hdr;
379              
380 0         0 $method = uc $method;
381              
382 0 0       0 if (my $hdr = $arg{headers}) {
383 0         0 while (my ($k, $v) = each %$hdr) {
384 0         0 $hdr{lc $k} = $v;
385             }
386             }
387              
388             # pseudo headers for all subsequent responses
389 0         0 my @pseudo = (URL => $url);
390 0 0       0 push @pseudo, Redirect => delete $arg{Redirect} if exists $arg{Redirect};
391              
392 0 0       0 my $recurse = exists $arg{recurse} ? delete $arg{recurse} : $MAX_RECURSE;
393              
394 0 0       0 return $cb->(undef, { @pseudo, Status => 599, Reason => "Too many redirections" })
395             if $recurse < 0;
396              
397 0 0       0 my $proxy = exists $arg{proxy} ? $arg{proxy} : $PROXY;
398 0   0     0 my $timeout = $arg{timeout} || $TIMEOUT;
399              
400 0         0 my ($uscheme, $uauthority, $upath, $query, undef) = # ignore fragment
401             $url =~ m|^([^:]+):(?://([^/?#]*))?([^?#]*)(?:(\?[^#]*))?(?:#(.*))?$|;
402              
403 0         0 $uscheme = lc $uscheme;
404              
405 0         0 my $uport = 3031;
406              
407 0 0       0 $uauthority =~ /^(?: .*\@ )? ([^\@]+?) (?: : (\d+) )?$/x
408             or return $cb->(undef, { @pseudo, Status => 599, Reason => "Unparsable URL" });
409              
410 0         0 my $uhost = lc $1;
411 0 0       0 $uport = $2 if defined $2;
412              
413             $hdr{host} = defined $2 ? "$uhost:$2" : "$uhost"
414 0 0       0 unless exists $hdr{host};
    0          
415              
416 0         0 $uhost =~ s/^\[(.*)\]$/$1/;
417 0 0       0 $upath .= $query if length $query;
418              
419 0         0 $upath =~ s%^/?%/%;
420              
421             # cookie processing
422 0 0       0 if (my $jar = $arg{cookie_jar}) {
423 0         0 my $cookies = cookie_jar_extract $jar, $uscheme, $uhost, $upath;
424              
425 0 0       0 $hdr{cookie} = join "; ", @$cookies
426             if @$cookies;
427             }
428              
429 0         0 my ($rhost, $rport, $rscheme, $rpath); # request host, port, path
430              
431 0 0       0 if ($proxy) {
432 0         0 ($rpath, $rhost, $rport, $rscheme) = ($url, @$proxy);
433              
434 0 0       0 $rscheme = "uwsgi" unless defined $rscheme;
435 0         0 $rhost = lc $rhost;
436 0         0 $rscheme = lc $rscheme;
437             } else {
438 0         0 ($rhost, $rport, $rscheme, $rpath) = ($uhost, $uport, $uscheme, $upath);
439             }
440              
441             # leave out fragment and query string, just a heuristic
442 0 0       0 $hdr{referer} = "$uscheme://$uauthority$upath" unless exists $hdr{referer};
443 0 0       0 $hdr{"user-agent"} = $USERAGENT unless exists $hdr{"user-agent"};
444              
445             $hdr{"content-length"} = length $arg{body}
446 0 0 0     0 if length $arg{body} || $method ne "GET";
447              
448 0         0 my $idempotent = $IDEMPOTENT{$method};
449              
450             # default value for keepalive is true iff the request is for an idempotent method
451 0 0       0 my $persistent = exists $arg{persistent} ? !!$arg{persistent} : $idempotent;
452 0 0       0 my $keepalive = exists $arg{keepalive} ? !!$arg{keepalive} : !$proxy;
453 0         0 my $was_persistent; # true if this is actually a recycled connection
454              
455             # the key to use in the keepalive cache
456 0         0 my $ka_key = "$uscheme\x00$uhost\x00$uport\x00$arg{sessionid}";
457              
458 0 0       0 $hdr{connection} = ($persistent ? $keepalive ? "keep-alive, " : "" : "close, ") . "Te"; #1.1
    0          
459 0 0       0 $hdr{te} = "trailers" unless exists $hdr{te}; #1.1
460              
461 0         0 my %state = (connect_guard => 1);
462              
463 0         0 my $ae_error = 595; # connecting
464              
465             # handle actual, non-tunneled, request
466             my $handle_actual_request = sub {
467 0     0   0 $ae_error = 596; # request phase
468              
469 0         0 my $hdl = $state{handle};
470 0         0 my ($lport, $lhost) = AnyEvent::Socket::unpack_sockaddr getsockname $hdl->fh;
471              
472              
473 0         0 my $env = {};
474 0 0       0 $env->{QUERY_STRING} = $query =~ m{^\?(.*)$} ? $1 : '';
475 0         0 $env->{REQUEST_METHOD} = $method;
476 0 0       0 $env->{CONTENT_LENGTH} = defined $hdr{"content-length"} ? $hdr{"content-length"} : '';
477 0 0       0 $env->{CONTENT_TYPE} = $method =~ /post/i ? 'application/x-www-form-urlencoded' : '';
478 0         0 $env->{REQUEST_URI} = $rpath;
479 0 0       0 $env->{PATH_INFO} = $rpath =~ m{^([^\?]+)} ? $1 : '';
480 0         0 $env->{SERVER_PROTOCOL}= 'HTTP/1.1';
481 0         0 $env->{REMOTE_ADDR} = AnyEvent::Socket::format_address($lhost);
482 0         0 $env->{REMOTE_PORT} = $lport;
483 0         0 $env->{SERVER_PORT} = $rport;
484 0         0 $env->{SERVER_NAME} = $rhost;
485              
486 0 0       0 if ($hdr{'x-uwsgi-nginx-compatible-mode'}) {
487 0         0 $env->{PATH_INFO} = Encode::decode('utf8', URI::Escape::XS::uri_unescape($env->{PATH_INFO}));
488             }
489              
490 0         0 foreach my $k (keys %hdr) {
491 0         0 (my $env_k = uc $k) =~ tr/-/_/;
492 0 0       0 $env->{"HTTP_$env_k"} = defined $hdr{$k} ? $hdr{$k} : '';
493             }
494              
495 0         0 my $data = '';
496 0         0 foreach my $k (sort keys %$env) {
497 0 0       0 die "Undef value found for $k" unless defined $env->{$k};
498 0         0 $data .= pack 'v/a*v/a*', map { Encode::encode('utf8', $_) } $k, $env->{$k};
  0         0  
499             }
500              
501             my $req_buf = pack('C1v1C1',
502             defined $arg{modifier1} ? $arg{modifier1} : 5, # default PSGI_MODIFIER1,
503             length($data),
504 0 0       0 defined $arg{modifier2} ? $arg{modifier2} : 0, # default PSGI_MODIFIER2,
    0          
505             ) . $data;
506              
507             # send request
508 0         0 $hdl->push_write($req_buf);
509              
510             # return if error occurred during push_write()
511 0 0       0 return unless %state;
512              
513             # reduce memory usage, save a kitten, also re-use it for the response headers.
514 0         0 %hdr = ();
515              
516             # status line and headers
517             $state{read_response} = sub {
518 0 0       0 return unless %state;
519              
520 0         0 for ("$_[1]") {
521 0         0 y/\015//d; # weed out any \015, as they show up in the weirdest of places.
522 0 0       0 /^HTTP\/0*([0-9\.]+) \s+ ([0-9]{3}) (?: \s+ ([^\012]*) )? \012/gxci
523             or return _error %state, $cb, { @pseudo, Status => 599, Reason => "Invalid server response" };
524              
525             # 100 Continue handling
526             # should not happen as we don't send expect: 100-continue,
527             # but we handle it just in case.
528             # since we send the request body regardless, if we get an error
529             # we are out of-sync, which we currently do NOT handle correctly.
530             return $state{handle}->push_read (line => $qr_nlnl, $state{read_response})
531 0 0       0 if $1 eq 100;
532              
533 0         0 push @pseudo,
534             HTTPVersion => $1,
535             Status => $2,
536             Reason => $3,
537             ;
538              
539 0 0       0 my $hdr = _parse_hdr
540             or return _error %state, $cb, { @pseudo, Status => 599, Reason => "Garbled response headers" };
541              
542 0         0 %hdr = (%$hdr, @pseudo);
543             }
544              
545             # redirect handling
546             # relative uri handling forced by microsoft and other shitheads.
547             # we give our best and fall back to URI if available.
548 0 0       0 if (exists $hdr{location}) {
549 0         0 my $loc = $hdr{location};
550              
551 0 0       0 if ($loc =~ m%^//%) { # //
    0          
    0          
552 0         0 $loc = "$rscheme:$loc";
553              
554             } elsif ($loc eq "") {
555 0         0 $loc = $url;
556              
557             } elsif ($loc !~ /^(?: $ | [^:\/?\#]+ : )/x) { # anything "simple"
558 0         0 $loc =~ s/^\.\/+//;
559              
560 0 0       0 if ($loc !~ m%^[.?#]%) {
    0          
561 0         0 my $prefix = "$rscheme://$uhost:$uport";
562              
563 0 0       0 unless ($loc =~ s/^\///) {
564 0         0 $prefix .= $upath;
565 0         0 $prefix =~ s/\/[^\/]*$//;
566             }
567              
568 0         0 $loc = "$prefix/$loc";
569              
570 0         0 } elsif (eval { require URI }) { # uri
571 0         0 $loc = URI->new_abs ($loc, $url)->as_string;
572              
573             } else {
574 0         0 return _error %state, $cb, { @pseudo, Status => 599, Reason => "Cannot parse Location (URI module missing)" };
575             }
576             }
577              
578 0         0 $hdr{location} = $loc;
579             }
580              
581 0         0 my $redirect;
582              
583 0 0       0 if ($recurse) {
584 0         0 my $status = $hdr{Status};
585              
586             # industry standard is to redirect POST as GET for
587             # 301, 302 and 303, in contrast to HTTP/1.0 and 1.1.
588             # also, the UA should ask the user for 301 and 307 and POST,
589             # industry standard seems to be to simply follow.
590             # we go with the industry standard. 308 is defined
591             # by rfc7538
592 0 0 0     0 if ($status == 301 or $status == 302 or $status == 303) {
    0 0        
      0        
593 0         0 $redirect = 1;
594             # HTTP/1.1 is unclear on how to mutate the method
595 0 0       0 unless ($method eq "HEAD") {
596 0         0 $method = "GET";
597 0         0 delete $arg{body};
598             }
599             } elsif ($status == 307 or $status == 308) {
600 0         0 $redirect = 1;
601             }
602             }
603              
604             my $finish = sub { # ($data, $err_status, $err_reason[, $persistent])
605 0 0       0 if ($state{handle}) {
606             # handle keepalive
607 0 0 0     0 if (
    0 0        
608             $persistent
609             && $_[3]
610             && ($hdr{HTTPVersion} < 1.1
611             ? $hdr{connection} =~ /\bkeep-?alive\b/i
612             : $hdr{connection} !~ /\bclose\b/i)
613             ) {
614 0         0 ka_store $ka_key, delete $state{handle};
615             } else {
616             # no keepalive, destroy the handle
617 0         0 $state{handle}->destroy;
618             }
619             }
620              
621 0         0 %state = ();
622              
623 0 0       0 if (defined $_[1]) {
624 0         0 $hdr{OrigStatus} = $hdr{Status}; $hdr{Status} = $_[1];
  0         0  
625 0         0 $hdr{OrigReason} = $hdr{Reason}; $hdr{Reason} = $_[2];
  0         0  
626             }
627              
628             # set-cookie processing
629 0 0       0 if ($arg{cookie_jar}) {
630 0         0 cookie_jar_set_cookie $arg{cookie_jar}, $hdr{"set-cookie"}, $uhost, $hdr{date};
631             }
632              
633 0 0 0     0 if ($redirect && exists $hdr{location}) {
634             # we ignore any errors, as it is very common to receive
635             # Content-Length != 0 but no actual body
636             # we also access %hdr, as $_[1] might be an erro
637             $state{recurse} =
638             uwsgi_request (
639             $method => $hdr{location},
640             %arg,
641             recurse => $recurse - 1,
642             Redirect => [$_[0], \%hdr],
643             sub {
644 0         0 %state = ();
645 0         0 &$cb
646             },
647 0         0 );
648             } else {
649 0         0 $cb->($_[0], \%hdr);
650             }
651 0         0 };
652              
653 0         0 $ae_error = 597; # body phase
654              
655 0         0 my $chunked = $hdr{"transfer-encoding"} =~ /\bchunked\b/i; # not quite correct...
656              
657 0 0       0 my $len = $chunked ? undef : $hdr{"content-length"};
658              
659             # body handling, many different code paths
660             # - no body expected
661             # - want_body_handle
662             # - te chunked
663             # - 2x length known (with or without on_body)
664             # - 2x length not known (with or without on_body)
665 0 0 0     0 if (!$redirect && $arg{on_header} && !$arg{on_header}(\%hdr)) {
    0 0        
    0 0        
    0 0        
    0 0        
      0        
666 0         0 $finish->(undef, 598 => "Request cancelled by on_header");
667             } elsif (
668             $hdr{Status} =~ /^(?:1..|204|205|304)$/
669             or $method eq "HEAD"
670             or (defined $len && $len == 0) # == 0, not !, because "0 " is true
671             ) {
672             # no body
673 0         0 $finish->("", undef, undef, 1);
674              
675             } elsif (!$redirect && $arg{want_body_handle}) {
676 0         0 $_[0]->on_eof (undef);
677 0         0 $_[0]->on_error (undef);
678 0         0 $_[0]->on_read (undef);
679              
680 0         0 $finish->(delete $state{handle});
681              
682             } elsif ($chunked) {
683 0         0 my $cl = 0;
684 0         0 my $body = "";
685 0   0     0 my $on_body = $arg{on_body} || sub { $body .= shift; 1 };
686              
687             $state{read_chunk} = sub {
688 0 0       0 $_[1] =~ /^([0-9a-fA-F]+)/
689             or return $finish->(undef, $ae_error => "Garbled chunked transfer encoding");
690              
691 0         0 my $len = hex $1;
692              
693 0 0       0 if ($len) {
694 0         0 $cl += $len;
695              
696             $_[0]->push_read (chunk => $len, sub {
697 0 0       0 $on_body->($_[1], \%hdr)
698             or return $finish->(undef, 598 => "Request cancelled by on_body");
699              
700             $_[0]->push_read (line => sub {
701 0 0       0 length $_[1]
702             and return $finish->(undef, $ae_error => "Garbled chunked transfer encoding");
703 0         0 $_[0]->push_read (line => $state{read_chunk});
704 0         0 });
705 0         0 });
706             } else {
707 0   0     0 $hdr{"content-length"} ||= $cl;
708              
709             $_[0]->push_read (line => $qr_nlnl, sub {
710 0 0       0 if (length $_[1]) {
711 0         0 for ("$_[1]") {
712 0         0 y/\015//d; # weed out any \015, as they show up in the weirdest of places.
713              
714 0 0       0 my $hdr = _parse_hdr
715             or return $finish->(undef, $ae_error => "Garbled response trailers");
716              
717 0         0 %hdr = (%hdr, %$hdr);
718             }
719             }
720              
721 0         0 $finish->($body, undef, undef, 1);
722 0         0 });
723             }
724 0         0 };
725              
726 0         0 $_[0]->push_read (line => $state{read_chunk});
727              
728             } elsif ($arg{on_body}) {
729 0 0       0 if (defined $len) {
730             $_[0]->on_read (sub {
731 0         0 $len -= length $_[0]{rbuf};
732              
733 0 0       0 $arg{on_body}(delete $_[0]{rbuf}, \%hdr)
734             or return $finish->(undef, 598 => "Request cancelled by on_body");
735              
736 0 0       0 $len > 0
737             or $finish->("", undef, undef, 1);
738 0         0 });
739             } else {
740             $_[0]->on_eof (sub {
741 0         0 $finish->("");
742 0         0 });
743             $_[0]->on_read (sub {
744 0 0       0 $arg{on_body}(delete $_[0]{rbuf}, \%hdr)
745             or $finish->(undef, 598 => "Request cancelled by on_body");
746 0         0 });
747             }
748             } else {
749 0         0 $_[0]->on_eof (undef);
750              
751 0 0       0 if (defined $len) {
752             $_[0]->on_read (sub {
753             $finish->((substr delete $_[0]{rbuf}, 0, $len, ""), undef, undef, 1)
754 0 0       0 if $len <= length $_[0]{rbuf};
755 0         0 });
756             } else {
757             $_[0]->on_error (sub {
758             ($! == Errno::EPIPE || !$!)
759             ? $finish->(delete $_[0]{rbuf})
760 0 0 0     0 : $finish->(undef, $ae_error => $_[2]);
761 0         0 });
762 0         0 $_[0]->on_read (sub { });
763             }
764             }
765 0         0 };
766              
767             # if keepalive is enabled, then the server closing the connection
768             # before a response can happen legally - we retry on idempotent methods.
769 0 0 0     0 if ($was_persistent && $idempotent) {
770 0         0 my $old_eof = $hdl->{on_eof};
771             $hdl->{on_eof} = sub {
772 0         0 _destroy_state %state;
773              
774 0         0 %state = ();
775             $state{recurse} =
776             uwsgi_request (
777             $method => $url,
778             %arg,
779             recurse => $recurse - 1,
780             persistent => 0,
781             sub {
782 0         0 %state = ();
783 0         0 &$cb
784             }
785 0         0 );
786 0         0 };
787             $hdl->on_read (sub {
788 0 0       0 return unless %state;
789              
790             # as soon as we receive something, a connection close
791             # once more becomes a hard error
792 0         0 $hdl->{on_eof} = $old_eof;
793 0         0 $hdl->push_read (line => $qr_nlnl, $state{read_response});
794 0         0 });
795             } else {
796 0         0 $hdl->push_read (line => $qr_nlnl, $state{read_response});
797             }
798 0         0 };
799              
800             my $prepare_handle = sub {
801 0     0   0 my ($hdl) = $state{handle};
802              
803             $hdl->on_error (sub {
804 0         0 _error %state, $cb, { @pseudo, Status => $ae_error, Reason => $_[2] };
805 0         0 });
806             $hdl->on_eof (sub {
807 0         0 _error %state, $cb, { @pseudo, Status => $ae_error, Reason => "Unexpected end-of-file" };
808 0         0 });
809 0         0 $hdl->timeout_reset;
810 0         0 $hdl->timeout ($timeout);
811 0         0 };
812              
813             # connected to proxy (or origin server)
814             my $connect_cb = sub {
815 0 0   0   0 my $fh = shift
816             or return _error %state, $cb, { @pseudo, Status => $ae_error, Reason => "$!" };
817              
818 0 0       0 return unless delete $state{connect_guard};
819              
820             # get handle
821             $state{handle} = new AnyEvent::Handle
822 0         0 %{ $arg{handle_params} },
  0         0  
823             fh => $fh,
824             peername => $uhost,
825             ;
826              
827 0         0 $prepare_handle->();
828              
829 0 0       0 delete $hdr{"proxy-authorization"} unless $proxy;
830 0         0 $handle_actual_request->();
831 0         0 };
832              
833             _get_slot $uhost, sub {
834 0     0   0 $state{slot_guard} = shift;
835              
836 0 0       0 return unless $state{connect_guard};
837              
838             # try to use an existing keepalive connection, but only if we, ourselves, plan
839             # on a keepalive request (in theory, this should be a separate config option).
840 0 0 0     0 if ($persistent && $KA_CACHE{$ka_key}) {
841 0         0 $was_persistent = 1;
842              
843 0         0 $state{handle} = ka_fetch $ka_key;
844             $state{handle}->destroyed
845 0 0       0 and die "AnyEvent::UWSGI: unexpectedly got a destructed handle (1), please report.";#d#
846 0         0 $prepare_handle->();
847             $state{handle}->destroyed
848 0 0       0 and die "AnyEvent::UWSGI: unexpectedly got a destructed handle (2), please report.";#d#
849 0         0 $handle_actual_request->();
850              
851             } else {
852             my $tcp_connect = $arg{tcp_connect}
853 0   0     0 || do { require AnyEvent::Socket; \&AnyEvent::Socket::tcp_connect };
854              
855 0   0     0 $state{connect_guard} = $tcp_connect->($rhost, $rport, $connect_cb, $arg{on_prepare} || sub { $timeout });
856             }
857 0         0 };
858              
859 0     0   0 defined wantarray && AnyEvent::Util::guard { _destroy_state %state }
860 0 0       0 }
861              
862             =item uwsgi_get
863              
864             Like C
865              
866             =cut
867             sub uwsgi_get($@) {
868 0     0 1 0 unshift @_, "GET";
869 0         0 &uwsgi_request
870             }
871              
872             =item uwsgi_head
873              
874             Like C
875              
876             =cut
877             sub uwsgi_head($@) {
878 0     0 1 0 unshift @_, "HEAD";
879 0         0 &uwsgi_request
880             }
881              
882             =item uwsgi_post
883              
884             Like C
885              
886             =cut
887             sub uwsgi_post($$@) {
888 0     0 1 0 my $url = shift;
889 0         0 unshift @_, "POST", $url, "body";
890 0         0 &uwsgi_request
891             }
892              
893             our @month = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
894             our @weekday = qw(Sun Mon Tue Wed Thu Fri Sat);
895              
896             sub format_date($) {
897 0     0 0 0 my ($time) = @_;
898              
899             # RFC 822/1123 format
900 0         0 my ($S, $M, $H, $mday, $mon, $year, $wday, $yday, undef) = gmtime $time;
901              
902 0         0 sprintf "%s, %02d %s %04d %02d:%02d:%02d GMT",
903             $weekday[$wday], $mday, $month[$mon], $year + 1900,
904             $H, $M, $S;
905             }
906              
907             sub parse_date($) {
908 0     0 0 0 my ($date) = @_;
909              
910 0         0 my ($d, $m, $y, $H, $M, $S);
911              
912 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          
913             # RFC 822/1123, required by RFC 2616 (with " ")
914             # cookie dates (with "-")
915              
916 0         0 ($d, $m, $y, $H, $M, $S) = ($1, $2, $3, $4, $5, $6);
917              
918             } 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$/) {
919             # RFC 850
920 0 0       0 ($d, $m, $y, $H, $M, $S) = ($1, $2, $3 < 69 ? $3 + 2000 : $3 + 1900, $4, $5, $6);
921              
922             } 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])$/) {
923             # ISO C's asctime
924 0         0 ($d, $m, $y, $H, $M, $S) = ($2, $1, $6, $3, $4, $5);
925             }
926             # other formats fail in the loop below
927              
928 0         0 for (0..11) {
929 0 0       0 if ($m eq $month[$_]) {
930 0         0 require Time::Local;
931 0         0 return eval { Time::Local::timegm ($S, $M, $H, $d, $_, $y) };
  0         0  
932             }
933             }
934              
935             undef
936 0         0 }
937              
938             sub set_proxy($) {
939 1 50   1 0 4 if (length $_[0]) {
940 0 0       0 $_[0] =~ m%^(uwsgi):// ([^:/]+) (?: : (\d*) )?%ix
941             or Carp::croak "$_[0]: invalid proxy URL";
942 0   0     0 $PROXY = [$2, $3 || 3128, $1]
943             } else {
944 1         2 undef $PROXY;
945             }
946             }
947              
948             # initialise proxy from environment
949             eval {
950             set_proxy $ENV{http_proxy};
951             };
952              
953             =back
954              
955             =head1 SEE ALSO
956            
957             L
958            
959             =head1 AUTHOR
960              
961             Alexander Kazakov, Evoland.kot@gmail.comE
962              
963             =head1 COPYRIGHT AND LICENSE
964              
965             Copyright (C) 2016 by Alexander Kazakov
966              
967             This library is free software; you can redistribute it and/or modify
968             it under the same terms as Perl itself, either Perl version 5.8.8 or,
969             at your option, any later version of Perl 5 you may have available.
970              
971             =cut
972              
973             1