File Coverage

blib/lib/Any/Daemon/HTTP/Proxy.pm
Criterion Covered Total %
statement 24 113 21.2
branch 0 50 0.0
condition 0 20 0.0
subroutine 8 26 30.7
pod 8 10 80.0
total 40 219 18.2


line stmt bran cond sub pod time code
1             # Copyrights 2013-2019 by [Mark Overmeer].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.02.
5             # This code is part of distribution Any-Daemon-HTTP. Meta-POD processed
6             # with OODoc into POD and HTML manual-pages. See README.md
7             # Copyright Mark Overmeer. Licensed under the same terms as Perl itself.
8              
9             package Any::Daemon::HTTP::Proxy;
10 1     1   7 use vars '$VERSION';
  1         2  
  1         53  
11             $VERSION = '0.28';
12              
13 1     1   5 use parent 'Any::Daemon::HTTP::Source';
  1         2  
  1         10  
14              
15 1     1   55 use warnings;
  1         2  
  1         42  
16 1     1   7 use strict;
  1         1  
  1         23  
17              
18 1     1   5 use Log::Report 'any-daemon-http';
  1         2  
  1         5  
19              
20 1     1   919 use LWP::UserAgent ();
  1         17797  
  1         29  
21 1     1   10 use HTTP::Status qw(HTTP_TOO_MANY_REQUESTS);
  1         2  
  1         60  
22 1     1   576 use Time::HiRes qw(time);
  1         1502  
  1         4  
23              
24              
25             sub init($)
26 0     0 0   { my ($self, $args) = @_;
27 0           $self->SUPER::init($args);
28              
29             $self->{ADHDP_ua} = $args->{user_agent}
30 0   0       || LWP::UserAgent->new(keep_alive => 30);
31              
32 0           $self->{ADHDP_via} = $args->{via};
33 0 0         if(my $fm = $args->{forward_map})
34 0 0   0     { $self->{ADHDP_map} = $fm eq 'RELAY' ? sub {$_[3]} : $fm;
  0            
35             }
36              
37 0 0         if(my $rem = $args->{remote_proxy})
38 0 0   0     { $self->{ADHDP_proxy} = ref $rem eq 'CODE' ? $rem : sub {$rem};
  0            
39             }
40              
41 0   0       $self->{ADHDP_fwd_to} = $args->{forward_timeout} // 100;
42              
43             # to be run before a request can be sent off
44             my @prepare =
45             ( $self->stripHeaders($args->{strip_req_headers})
46             , $self->addHeaders ($args->{add_req_headers})
47             , $args->{change_request} || ()
48 0   0       );
49              
50             # to be run before a response is passed on the the client
51             my @postproc =
52             ( $self->stripHeaders($args->{strip_resp_headers})
53             , $self->addHeaders ($args->{add_resp_headers})
54             , $args->{change_response} || ()
55 0   0       );
56              
57 0           $self->{ADHDP_prepare} = \@prepare;
58 0           $self->{ADHDP_postproc} = \@postproc;
59 0           $self;
60             }
61              
62             #-----------------
63              
64 0     0 1   sub userAgent() {shift->{ADHDP_ua}}
65 0     0 1   sub via() {shift->{ADHDP_via}}
66 0     0 0   sub forwardMap(){shift->{ADHDP_map}}
67              
68              
69             sub remoteProxy(@)
70 0     0 1   { my $rem = shift->{ADHDP_proxy};
71 0 0         $rem ? $rem->(@_) : undef;
72             }
73              
74             #-----------------
75              
76             my $last_used_proxy = '';
77             sub _collect($$$$)
78 0     0     { my ($self, $vhost, $session, $req, $rel_uri) = @_;
79 0           my $resp;
80              
81 0 0         my $vhost_name = $vhost ? $vhost->name : '';
82 0   0       my $tohost = $req->header('Host') || $vhost_name;
83              
84             #XXX MO: need to support https as well
85 0           my $uri = URI->new_abs($rel_uri, "http://$tohost");
86              
87             # Via: RFC2616 section 14.45
88 0   0       my $my_via = '1.1 ' . ($self->via // $vhost_name);
89 0 0         if(my $via = $req->header('Via'))
90 0           { foreach (split /\,\s+/, $via)
91 0 0         { return HTTP::Response->new(HTTP_TOO_MANY_REQUESTS)
92             if $_ ne $my_via;
93             }
94 0           $req->header(Via => "$via, $my_via");
95             }
96             else
97 0           { $req->push_header(Via => $my_via);
98             }
99              
100             $self->$_($req, $uri)
101 0           for @{$self->{ADHDP_prepare}};
  0            
102              
103 0           my $ua = $self->userAgent;
104 0           my @proxies = grep defined, $self->remoteProxy(HTTP => $session,$req,$uri);
105              
106 0 0         if(@proxies)
107 0           { $self->proxify($req, $uri);
108 0 0         if($proxies[0] ne $last_used_proxy)
109             { # put last_used_proxy as first try. UserAgent reuses connection
110 0 0         @proxies = ($last_used_proxy, grep $_ ne $last_used_proxy, @proxies)
111             if grep $_ eq $last_used_proxy, @proxies;
112             }
113              
114 0           my $start = time;
115 0           my $timeout = 3;
116 0           while(time - $start < $self->{ADHDP_fwd_to})
117 0           { my $proxy = shift @proxies;
118              
119             # redirect to next proxy
120 0 0         $ua->proxy($uri->scheme, $proxy)
121             if $proxy ne $last_used_proxy;
122              
123 0           $last_used_proxy = $proxy;
124 0           $ua->timeout($timeout);
125              
126 0           my $start_req = time;
127 0           $resp = $ua->request($req);
128              
129 0           info __x"request {method} {uri} via {proxy}: {status} in {t%d}ms"
130             , method => $req->method, uri => "$uri", proxy => $proxy
131             , status => $resp->code, t => (time-$start_req)*1000;
132              
133 0 0         last unless $resp->is_error;
134              
135 0           $timeout++; # each attempt waits one second longer
136              
137             # rotate attempted proxies
138 0           push @proxies, $proxy;
139             }
140             }
141             else
142 0           { $ua->proxy($uri->scheme, undef);
143 0           $last_used_proxy = '';
144              
145 0           $ua->timeout(180);
146 0           $resp = $ua->request($req);
147 0           info __x"request {method} {uri} without proxy: {status}"
148             , method => $req->method, uri => "$uri", status => $resp->code;
149             }
150              
151             $self->$_($resp, $uri)
152 0           for @{$self->{ADHDP_postproc}};
  0            
153              
154 0           $resp;
155             }
156              
157              
158             sub stripHeaders(@)
159 0     0 1   { my $self = shift;
160 0           my @strip;
161 0 0         foreach my $field (@_ > 1 ? @_ : ref $_[0] eq 'ARRAY' ? @{$_[0]} : shift)
  0 0          
162             { push @strip
163 0     0     , !ref $field ? sub {$_[0]->remove_header($field)}
164             : ref $field eq 'CODE' ? $field
165             : ref $field eq 'Regex' ? sub {
166 0     0     my @kill = grep $_ =~ $field, $_[0]->header_field_names;
167 0           $_[0]->remove_header($_) for @kill;
168             }
169 0 0         : panic "do not understand $field";
    0          
    0          
170             }
171              
172 0 0         @strip or return;
173 0     0     sub { my $header = $_[1]->headers; $_->($header) for @strip };
  0            
  0            
174             }
175              
176              
177             sub addHeaders($@)
178 0     0 1   { my $self = shift;
179 0 0 0       return if @_==1 && ref $_[0] eq 'CODE';
180              
181 0 0         my @pairs = @_ > 1 ? @_ : defined $_[0] ? @{$_[0]} : ();
  0 0          
182 0 0   0     @pairs or return sub {};
183              
184 0     0     sub { $_[1]->push_header(@pairs) };
  0            
185             }
186              
187              
188             sub proxify($$)
189 0     0 1   { my ($self, $request, $uri) = @_;
190 0           $request->uri($uri);
191 0           $request->header(Host => $uri->authority);
192             }
193              
194              
195             sub forwardRewrite($$$)
196 0     0 1   { my ($self, $session, $req, $uri) = @_;
197 0 0         $self->allow($session, $req, $uri) or return;
198 0 0         my $mapper = $self->forwardMap or return;
199 0           $mapper->(@_);
200             }
201              
202              
203             sub forwardRequest($$$)
204 0     0 1   { my ($self, $session, $req, $uri) = @_;
205 0           $self->_collect(undef, $session, $req, $uri);
206             }
207              
208             #----------------
209              
210             1;