File Coverage

blib/lib/Weixin/UserAgent.pm
Criterion Covered Total %
statement 18 110 16.3
branch 0 38 0.0
condition 0 48 0.0
subroutine 6 21 28.5
pod 0 7 0.0
total 24 224 10.7


line stmt bran cond sub pod time code
1             package Weixin::UserAgent;
2            
3 1     1   462 use AnyEvent::HTTP ();
  1         25266  
  1         30  
4 1     1   579 use HTTP::Cookies ();
  1         4354  
  1         18  
5 1     1   5 use HTTP::Request ();
  1         8  
  1         8  
6 1     1   369 use HTTP::Request::Common ();
  1         1342  
  1         15  
7 1     1   3 use HTTP::Response ();
  1         2  
  1         169  
8            
9             sub new {
10 0     0 0   my $class = shift;
11 0           my %p = @_;
12 0   0       return bless {
      0        
      0        
      0        
      0        
13             agent => $p{agent} || $AnyEvent::HTTP::USERAGENT . ' AnyEvent-UserAgent/' . $VERSION ,
14             cookie_jar => $p{cookie_jar} || HTTP::Cookies->new,
15             max_redirects => $p{max_redirects} || 5,
16             inactivity_timeout => $p{inactivity_timeout} || 20,
17             request_timeout => $p{request_timeout} || 0
18             },$class;
19             }
20             sub request {
21 0     0 0   my $cb = pop();
22 0           my ($self, $req, %opts) = @_;
23             $self->_request($req, \%opts, sub {
24 0     0     $self->_response($req, @_, $cb);
25 0           });
26             }
27            
28 0     0 0   sub get { _make_request(GET => @_) }
29 0     0 0   sub head { _make_request(HEAD => @_) }
30 0     0 0   sub put { _make_request(PUT => @_) }
31 0     0 0   sub delete { _make_request(DELETE => @_) }
32 0     0 0   sub post { _make_request(POST => @_) }
33            
34             sub _make_request {
35 0     0     my $cb = pop();
36 0           my $meth = shift();
37 0           my $self = shift();
38            
39 1     1   3 no strict 'refs';
  1         1  
  1         687  
40 0           $self->request(&{'HTTP::Request::Common::' . $meth}(@_), $cb);
  0            
41             }
42            
43             sub _request {
44 0     0     my ($self, $req, $opts, $cb) = @_;
45            
46 0           my $uri = $req->uri;
47 0           my $hdrs = $req->headers;
48            
49 0 0         unless ($hdrs->user_agent) {
50 0           $hdrs->user_agent($self->{agent});
51             }
52            
53 0 0 0       if ($uri->can('userinfo') && $uri->userinfo && !$hdrs->authorization) {
      0        
54 0           $hdrs->authorization_basic(split(':', $uri->userinfo, 2));
55             }
56 0 0         if ($uri->scheme) {
57 0           $self->{cookie_jar}->add_cookie_header($req);
58             }
59            
60 0           for (qw(max_redirects inactivity_timeout request_timeout)) {
61 0 0         $opts->{$_} = $self->{$_} unless exists($opts->{$_});
62             }
63            
64 0           my ($grd, $tmr);
65            
66 0 0         if ($opts->{request_timeout}) {
67             $tmr = AE::timer $opts->{request_timeout}, 0, sub {
68 0     0     undef($grd);
69 0           $cb->($opts, undef, {Status => 597, Reason => 'Request timeout'});
70 0           };
71             }
72 0           $grd = AnyEvent::HTTP::http_request(
73             $req->method,
74             $req->uri,
75 0           headers => {map { $_ => $hdrs->header($_) } $hdrs->header_field_names},
76             body => $req->content,
77             recurse => 0,
78             timeout => $opts->{inactivity_timeout},
79 0           (map { $_ => $opts->{$_} } grep { exists($opts->{$_}) }
80             qw(proxy tls_ctx session timeout on_prepare tcp_connect on_header
81             on_body want_body_handle persistent keepalive handle_params)),
82             sub {
83 0     0     undef($grd);
84 0           undef($tmr);
85 0           $cb->($opts, @_);
86             }
87 0           );
88             }
89            
90             sub _response {
91 0     0     my $cb = pop();
92 0           my ($self, $req, $opts, $body, $hdrs, $prev, $count) = @_;
93            
94 0           my $res = HTTP::Response->new(delete($hdrs->{Status}), delete($hdrs->{Reason}));
95            
96 0           $res->request($req);
97 0 0         $res->previous($prev) if $prev;
98            
99 0           delete($hdrs->{URL});
100 0 0         if (defined($hdrs->{HTTPVersion})) {
101 0           $res->protocol('HTTP/' . delete($hdrs->{HTTPVersion}));
102             }
103 0 0         if (my $hdr = $hdrs->{'set-cookie'}) {
104             # Split comma-concatenated "Set-Cookie" values.
105             # Based on RFC 6265, section 4.1.1.
106 0           local @_ = split(/,([\w.!"'%\$&*+-^`]+=)/, ',' . $hdr);
107 0           shift();
108 0           my @val;
109 0           push(@val, join('', shift(), shift())) while @_;
110 0           $hdrs->{'set-cookie'} = \@val;
111             }
112 0 0         if (keys(%$hdrs)) {
113 0           $res->header(%$hdrs);
114             }
115 0 0 0       if ($res->code >= 590 && $res->code <= 599 && $res->message) {
      0        
116 0 0         if ($res->message eq 'Connection timed out') {
117 0           $res->message('Inactivity timeout');
118             }
119 0 0         unless ($res->header('client-warning')) {
120 0           $res->header('client-warning' => $res->message);
121             }
122             }
123 0 0         if (defined($body)) {
124 0           $res->content_ref(\$body);
125             }
126 0           $self->{cookie_jar}->extract_cookies($res);
127            
128 0           my $code = $res->code;
129            
130 0 0 0       if ($code == 301 || $code == 302 || $code == 303 || $code == 307 || $code == 308) {
      0        
      0        
      0        
131 0           $self->_redirect($req, $opts, $code, $res, $count, $cb);
132             }
133             else {
134 0           $cb->($res);
135             }
136             }
137            
138             sub _redirect {
139 0     0     my ($self, $req, $opts, $code, $prev, $count, $cb) = @_;
140            
141 0 0         unless (defined($count) ? $count : ($count = $opts->{max_redirects})) {
    0          
142 0           $prev->header('client-warning' => 'Redirect loop detected (max_redirects = ' . $opts->{max_redirects} . ')');
143 0           $cb->($prev);
144 0           return;
145             }
146            
147 0           my $meth = $req->method;
148 0           my $proto = $req->uri->scheme;
149 0           my $uri = $prev->header('location');
150            
151 0           $req = $req->clone();
152 0           $req->remove_header('cookie');
153 0 0 0       if (($code == 302 || $code == 303) && !($meth eq 'GET' || $meth eq 'HEAD')) {
      0        
      0        
154 0           $req->method('GET');
155 0           $req->content('');
156 0           $req->remove_content_headers();
157             }
158             {
159             # Support for relative URL for redirect.
160             # Not correspond to RFC.
161 0           local $URI::ABS_ALLOW_RELATIVE_SCHEME = 1;
  0            
162 0           my $base = $prev->base;
163 0 0         $uri = $HTTP::URI_CLASS->new(defined($uri) ? $uri : '', $base)->abs($base);
164             }
165 0           $req->uri($uri);
166 0 0 0       if ($proto eq 'https' && $uri->scheme eq 'http') {
167             # Suppress 'Referer' header for HTTPS to HTTP redirect.
168             # RFC 2616, section 15.1.3.
169 0           $req->remove_header('referer');
170             }
171            
172             $self->_request($req, $opts, sub {
173 0     0     $self->_response($req, @_, $prev, $count - 1, sub { return $cb->(@_); });
  0            
174 0           });
175             }
176            
177            
178             1;
179            
180            
181             __END__