Apache2-Proxy
view release on metacpan or search on metacpan
lib/Apache2/Proxy.pm view on Meta::CPAN
use URI;
use Net::HTTP;
use HTTP::Response;
use HTTP::Headers;
use HTTP::Headers::Util ();
use Data::Dumper;
use Net::DNS;
our $Resolver = Net::DNS::Resolver->new;
use constant DEBUG => 1;
use constant VERBOSE_DEBUG => 0;
use constant MAX_CONTENT_LENGTH => 131072; # 128k
# firefox default headers
our %Headers = (
'Accept-Encoding' => 'gzip,deflate',
'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
'Accept-Lang' => 'en-us,en;q=0.5',
'Accept' =>
'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
'User-Agent' =>
'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.0.10) Gecko/2009042315 Firefox/3.0.10',
);
our %Response = (
200 => 'twohundred',
204 => 'twoohfour',
206 => 'twoohsix',
301 => 'threeohone',
302 => 'redirect',
303 => 'redirect',
304 => 'threeohfour',
307 => 'redirect',
400 => 'bsod',
401 => 'bsod',
403 => 'bsod',
404 => 'bsod',
410 => 'bsod',
500 => 'bsod',
502 => 'bsod',
503 => 'bsod',
504 => 'bsod',
);
# handles common proxy functions
# takes $r and returns the http headers
sub get_request_headers {
my ( $class, $r ) = @_;
my %headers;
$r->headers_in->do(
sub {
my $k = shift;
my $v = shift;
if ( $k =~ m/^connection/i ) {
$headers{$k} = 'keep-alive';
return 1;
}
# pass this header onto the remote request
$headers{$k} = $v;
return 1; # don't remove me or you will burn in hell baby
}
);
# work around clients which don't support compression
if ( !exists $headers{'Accept-Encoding'} ) {
$r->log->debug(
"$$ client DOES NOT support compression " . Dumper( \%headers ) )
if VERBOSE_DEBUG;
# set default outgoing compression headers
$headers{'Accept-Encoding'} = 'gzip, deflate';
}
else {
$r->log->debug(
"$$ client supports compression " . $headers{'Accept-Encoding'} )
if VERBOSE_DEBUG;
$r->pnotes(
client_supports_compression => $headers{'Accept-Encoding'} );
}
$r->log->debug( "$$ proxy request headers " . Dumper( \%headers ) )
if DEBUG;
return \%headers;
}
# Takes an HTTP::Response object, clears the response headers,
# adds cookie and auth headers, and additional headers
# Sets the Server header to sl if it is not defined.
sub set_response_headers {
my ( $class, $r, $res ) = @_;
#############################
# clear the current headers
$r->headers_out->clear();
$class->translate_cookie_and_auth_headers( $r, $res );
#########################
# Create a hash with the remaining HTTP::Response HTTP::Headers attributes
my %headers;
$res->scan( sub { $headers{ $_[0] } = $_[1]; } );
##########################################
# this is for any additional headers, usually site specific
$class->translate_remaining_headers( $r, \%headers );
# set the server header
$headers{Server} ||= __PACKAGE__;
$r->log->debug( "$$ server header is " . $headers{Server} )
if VERBOSE_DEBUG;
$r->server->add_version_component( $headers{Server} );
return 1;
}
sub translate_remaining_headers {
my ( $class, $r, $headers ) = @_;
foreach my $key ( keys %{$headers} ) {
# we set this manually
next if lc($key) eq 'server';
# skip HTTP::Response inserted headers
next if substr( lc($key), 0, 6 ) eq 'client';
# let apache set these
next if substr( lc($key), 0, 10 ) eq 'connection';
next if substr( lc($key), 0, 10 ) eq 'keep-alive';
# some headers have an unecessary newline appended so chomp the value
chomp( $headers->{$key} );
if ( $headers->{$key} =~ m/\n/ ) {
$headers->{$key} =~ s/\n/ /g;
}
$r->log->debug(
"$$ Setting header key $key, value " . $headers->{$key} )
if VERBOSE_DEBUG;
$r->headers_out->set( $key => $headers->{$key} );
}
return 1;
}
sub translate_cookie_and_auth_headers {
my ( $class, $r, $res ) = @_;
################################################
# process the www-auth and set-cookie headers
no strict 'refs';
foreach my $header_type qw( set-cookie www-authenticate ) {
next unless defined $res->header($header_type);
my @headers = $res->header($header_type);
foreach my $header (@headers) {
$r->log->debug("$$ setting header $header_type value $header")
if VERBOSE_DEBUG;
$r->err_headers_out->add( $header_type => $header );
}
# and remove it from the response headers
my $removed = $res->headers->remove_header($header_type);
$r->log->debug("$$ translated $removed $header_type headers")
if VERBOSE_DEBUG;
}
return 1;
}
sub set_twohundred_response_headers {
my ( $class, $r, $res, $response_content_ref ) = @_;
$r->log->debug("setting response headers " . Dumper($res->headers)) if DEBUG;
# This loops over the response headers and adds them to headers_out.
# Override any headers with our own here
my %headers;
$r->headers_out->clear();
$class->translate_cookie_and_auth_headers( $r, $res );
# Create a hash with the HTTP::Response HTTP::Headers attributes
$res->scan( sub { $headers{ $_[0] } = $_[1]; } );
$r->log->debug(
sprintf( "$$ not cookie/auth headers: %s", Dumper( \%headers ) ) )
if VERBOSE_DEBUG;
lib/Apache2/Proxy.pm view on Meta::CPAN
}
else {
#$Cache->add_known_not_html( $url => $response->content_type );
}
$r->log->debug( "$$ 200 for $url, length "
. length( $response->decoded_content )
. " bytes" )
if DEBUG;
my $response_content_ref = \$response->decoded_content;
# set the status line
$r->status_line( $response->status_line );
$r->log->debug( "$$ status line is " . $response->status_line )
if DEBUG;
# set the response headers
my $set_ok =
$class->set_twohundred_response_headers( $r, $response,
$response_content_ref );
if (VERBOSE_DEBUG) {
$r->log->debug( "$$ Response content: " . $$response_content_ref );
}
# rflush() flushes the headers to the client
# thanks to gozer's mod_perl for speed presentation
$r->rflush();
my $bytes_sent = $r->print($$response_content_ref);
$r->log->debug("$$ bytes sent: $bytes_sent") if DEBUG;
return Apache2::Const::DONE;
}
sub get {
my ( $class, $args_ref ) = @_;
unless ( $args_ref->{url} ) {
warn("$$ no url passed, returning");
return;
}
my $url = $args_ref->{url};
my $host = $args_ref->{host} || $args_ref->{headers}->{Host} || 'localhost';
my $port = $args_ref->{port} || 80;
$url = URI->new($url) or die("Unable to parse url '$url'.");
my $headers = $args_ref->{headers} || \%Headers;
# convert headers to array-ref if a hash-ref is passed
$headers = [%$headers] if ( ref $headers eq 'HASH' );
my $http = Net::HTTP->new(
Host => $url->host,
PeerAddr => $host,
PeerPort => $port
) || die $@;
# set keep alive
$http->keep_alive(1);
# reinforce the point (Net::HTTP adds PeerPort to host during
# new())
$http->host( $url->host );
# make the request
my $req = $url->path_query || "/";
my $ok = $http->write_request( GET => $req, @$headers );
# get the result code, message and response headers
my ( $code, $mess, @headers_out ) = $http->read_response_headers;
# read response body
my $body = "";
my $response = _build_response( $code, $mess, \@headers_out, \$body );
# is this response too big?
my $content_length = $response->headers->header('Content-Length') || 0;
return if ( $content_length > MAX_CONTENT_LENGTH );
while (1) {
my $buf;
my $n = $http->read_entity_body( $buf, 10240 );
die "read failed: $!" unless defined $n;
last unless $n;
$body .= $buf;
return if ( length($body) > MAX_CONTENT_LENGTH );
}
$response->content_ref( \$body );
return $response;
}
# turns data returned by Net::HTTP into a HTTP::Response object
sub _build_response {
my ( $code, $mess, $header_list, $body_ref ) = @_;
my $header = HTTP::Headers->new(@$header_list);
my $response = HTTP::Response->new( $code, $mess, $header, $$body_ref );
return $response;
}
# adds a convenient extra method for inspection
{
no warnings;
*HTTP::Response::is_html = sub {
return 1 if ( shift->content_type =~ m/text\/html/ );
return;
};
*HTTP::Response::should_compress = sub {
$" = '|';
my @compressibles
; # = qw( text/html text/xml text/plain application/pdf );
return 1 if ( shift->content_type =~ m/(?:@compressibles)/ );
return;
};
( run in 0.525 second using v1.01-cache-2.11-cpan-df04353d9ac )