File Coverage

blib/lib/Apache/ReverseProxy.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package Apache::ReverseProxy;
2              
3             # Copyright (c) 1999-2005 Clinton Wong.
4             # Additional modifications Copyright (c) 2000 David Jao.
5             # Additional modifications Copyright (c) 2005 Penny Leach.
6             # All rights reserved.
7              
8             # This program is free software; you can redistribute it
9             # and/or modify it under the same terms as Perl itself.
10              
11             # This is based on Apache::ProxyPass, by Michael
12             # Smith , which is based on Apache::ProxyPassThru.
13              
14 1     1   643 use strict;
  1         2  
  1         31  
15 1     1   1339 use Apache::Constants ':common';
  0            
  0            
16             use LWP;
17             use CGI;
18             use Symbol 'gensym';
19             use vars qw($VERSION);
20             $VERSION = '0.07';
21              
22             sub handler {
23              
24             my $r = shift;
25             my %regex;
26             my %exact;
27             my %cookie_trans;
28             my %no_query_replace;
29              
30             # figure out the config file
31             my $conf = $r->dir_config('ReverseProxyConfig');
32             if (! defined $conf ) {
33             $r->log_error("ReverseProxyConfig directive not defined");
34             return DECLINED;
35             }
36              
37             my $chain = $r->dir_config('ReverseProxyChain');
38             my $noproxy = $r->dir_config('ReverseProxyNoChain');
39              
40             # read config file
41             my $f = gensym();
42             if (! open($f, $conf) ) {
43             $r->log_error("Couldn't open config file: " . $conf);
44             return DECLINED;
45             }
46              
47             while (my $line=<$f>) {
48             chomp($line);
49             $line =~ s/(^\s+)|(\s+$)//; # kill leading,trailing space
50             next if (substr($line,0,1) eq '#'); # skip comments
51            
52             if ($line =~ /^([^\s]+)\s+([^\s]+)(\s+([^\s]+)){0,1}/) {
53             my $from = $1; my $to=$2; my $options = $3 || '';
54              
55             # do URL mappings
56             if ($options =~ /exact/i) { $exact{$from} = $to }
57             else { $regex{$from} = $to }
58             if ($options =~ /cookietrans/i) { $cookie_trans{$from} = 1 }
59             if ($options =~ /noquerykeyreplace/i) { $no_query_replace{$from} = 1 }
60              
61             } # if valid line
62             } # while config file input
63             close($f);
64              
65             my $uri = $r->uri();
66              
67             my $uri_with_qs = $uri;
68             my $query = $r->args() || ''; # from the user's request
69             if (length $query) { $uri_with_qs .= '?' . $query }
70            
71             my $changed=0;
72              
73              
74             if ( defined $exact{$uri} ) { # try an exact uri match first
75             $uri = $exact{$uri};
76             $changed=1;
77             }
78             elsif ( defined $exact{$uri_with_qs} ) { # try exact uri with qs
79             $uri = $exact{$uri_with_qs};
80             $changed=1;
81             }
82             else {
83              
84             # otherwise, try regular expression matching
85             foreach my $key (keys(%regex)) {
86             if ($uri =~ /^$key/) {
87              
88             $changed=1;
89              
90             # replace URI's first, then append query string
91             my $replace_uri = $regex{$key};
92             my $replace_query='';
93             if ($replace_uri =~ s/\?(.*)$//) { $replace_query = $1 }
94              
95             $uri =~ s/$key/${replace_uri}/;
96             if (length $replace_query) { $uri .= '?' . $replace_query }
97             last;
98             }
99             } # for each regex match...
100              
101             } # regex matching
102              
103              
104             if ($changed) {
105              
106             # strip out possible query string from re-written uri, store it
107             my $munged_uri = $uri;
108             my $munged_uri_query = '';
109             if ($munged_uri =~ s/\?(.*)$//) { $munged_uri_query = $1 }
110              
111             # query string processing
112             my $query = $r->args() || ''; # from the user's request
113            
114             # user has query, but munged url doesn't
115             if ( defined $query && length($query) && length($munged_uri_query)==0) {
116             $munged_uri_query = $query;
117             }
118             elsif (defined $query && length($query)) {
119             # if the user had a query string, add it in to the munged uri's qs
120             my $internal = new CGI($munged_uri_query);
121             my $user_query = new CGI($query);
122             foreach my $user_key ( $user_query->param() ) {
123             # if we can't replace and the variable exists in both places, skip it
124              
125             # unless ($internal->param($user_key) && $user_query->param($user_key)
126             # && defined $no_query_replace{$orig_uri} ) {
127              
128             $internal->param($user_key, $user_query->param($user_key) );
129             # }
130             } # for each variable in the user's query string
131             $munged_uri_query = $internal->query_string(); # stringify
132             }
133              
134             if (length $munged_uri_query) { $uri = $munged_uri .'?'. $munged_uri_query }
135              
136              
137             my $request = new HTTP::Request($r->method, $uri);
138              
139             # copy in client headers
140             my(%headers) = $r->headers_in();
141             for (keys(%headers)) {
142             $request->header($_, $headers{$_});
143             }
144            
145             my $host = $uri;
146             $host =~ s/([a-zA-z]*:\/\/)([a-zA-Z0-9.-]*)([:0-9]*)\/.*/$2/;
147             $request->header('Host', $host);
148             my $ua = new LWP::UserAgent('max_redirect' => 0);
149              
150             if (defined $chain) {
151             $ua->proxy(['http', 'https', 'ftp', 'gopher'], $chain);
152             if (defined $noproxy) { $ua->noproxy($noproxy) }
153             }
154            
155             # copy over the client's user-agent, since some servers look at
156             # this and customize their response based on it.
157              
158             my $origin_ua = $r->header_in('user-agent');
159             if (defined $origin_ua && length $origin_ua) {
160             $ua->agent($origin_ua)
161             }
162              
163             # copy over the content type
164             my $content_type = $r->header_in('content-type');
165             if (defined $content_type && length $content_type) {
166             $request->header('content-type', $content_type);
167             }
168              
169             # copy over the entity body as well
170             my $entity_body = $r->content();
171             if (defined $entity_body && length $entity_body) {
172             $request->content($entity_body);
173             } else {
174             my $buff = '';
175             $r->read($buff, $r->header_in('Content-length'));
176             if ($buff ne '') {
177             $request->content($buff);
178             }
179             }
180              
181             # Okay now for the fireworks. We use a custom subroutine to send an
182             # http header and then display the content in chunks of 4096 bytes.
183             # In this way we avoid reading the entire request into core and forcing
184             # the web browser to wait for the entire file to be downloaded before
185             # receiving any data.
186             my $first_time=1;
187             my $response = $ua->request($request, sub {
188             my($data, $response, $protocol) = @_;
189             if ($first_time == 1) {
190             $r->content_type($response->header('Content-type'));
191             $r->status($response->code());
192              
193             $r->status_line($response->code() . ' ' . $response->message());
194              
195             $response->scan(sub { $r->headers_out->add(@_); });
196             $r->send_http_header();
197             $first_time=0;
198             }
199             print "$data";
200             }
201             , 4096);
202             # If the custom subroutine above did not get called, that means our
203             # http request must have failed (c.f. LWP::UserAgent documentation).
204             # We handle that case here.
205             if ($first_time == 1) {
206             $r->content_type($response->header('Content-type'));
207             $r->status($response->code());
208              
209             $r->status_line($response->code() . ' ' . $response->message());
210              
211             $response->scan(sub { $r->headers_out->add(@_); });
212             $r->send_http_header();
213             $first_time=0;
214             print $response->content();
215             }
216             return OK;
217              
218             } # if uri changed
219              
220             return DECLINED;
221              
222             } # handler
223              
224             1;
225             __END__