File Coverage

blib/lib/Plack/App/Proxy.pm
Criterion Covered Total %
statement 29 61 47.5
branch 1 16 6.2
condition 1 13 7.6
subroutine 9 15 60.0
pod 2 6 33.3
total 42 111 37.8


line stmt bran cond sub pod time code
1             package Plack::App::Proxy;
2              
3 9     9   208433 use strict;
  9         21  
  9         321  
4 9     9   265 use 5.008_001;
  9         28  
  9         359  
5 9     9   14414 use parent 'Plack::Component';
  9         6017  
  9         51  
6 9     9   232825 use Plack::Util::Accessor qw/remote preserve_host_header backend options/;
  9         3208  
  9         72  
7 9     9   10530 use Plack::Request;
  9         700718  
  9         314  
8 9     9   92 use Plack::Util;
  9         19  
  9         243  
9 9     9   49 use HTTP::Headers;
  9         18  
  9         6767  
10              
11             our $VERSION = '0.29';
12              
13             sub prepare_app {
14 4     4 1 418 my $self = shift;
15 4 50 50     54 $self->backend($ENV{PLACK_PROXY_BACKEND} || 'AnyEvent::HTTP') unless defined $self->backend;
16             }
17              
18             # hop-by-hop headers (see also RFC2616)
19             my @hop_by_hop = qw(
20             Connection Keep-Alive Proxy-Authenticate Proxy-Authorization
21             TE Trailer Transfer-Encoding Upgrade
22             );
23              
24             sub filter_headers {
25 1     1 0 232 my $self = shift;
26 1         2 my ( $headers ) = @_;
27              
28             # Save from known hop-by-hop deletion.
29 1         3 my @connection_tokens = $headers->header('Connection');
30              
31             # Remove hop-by-hop headers.
32 1         25 $headers->remove_header( $_ ) for @hop_by_hop;
33              
34             # Connection header's tokens are also hop-by-hop.
35 1         72 for my $token ( @connection_tokens ){
36 1         9 $headers->remove_header( $_ ) for split /\s*,\s*/, $token;
37             }
38             }
39              
40             sub build_url_from_env {
41 0     0 0   my($self, $env) = @_;
42              
43 0 0         return $env->{'plack.proxy.url'}
44             if exists $env->{'plack.proxy.url'};
45              
46 0 0 0       my $url = $env->{'plack.proxy.remote'} || $self->remote
47             or return;
48              
49             # avoid double slashes
50 0 0 0       $url =~ s!/$!! unless $env->{SCRIPT_NAME} && $env->{SCRIPT_NAME} =~ m!/$!;
51              
52 0   0       $url .= $env->{PATH_INFO} || '';
53 0 0 0       $url .= '?' . $env->{QUERY_STRING} if defined $env->{QUERY_STRING} && length $env->{QUERY_STRING} > 0;
54              
55 0           return $url;
56             }
57              
58             sub build_headers_from_env {
59 0     0 0   my($self, $env, $req) = @_;
60              
61 0           my $headers = $req->headers->clone;
62 0           $headers->header("X-Forwarded-For" => $env->{REMOTE_ADDR});
63 0 0         $headers->remove_header("Host") unless $self->preserve_host_header;
64 0           $self->filter_headers( $headers );
65              
66 0           +{ map {$_ => scalar $headers->header($_) } $headers->header_field_names };
  0            
67             }
68              
69             sub call {
70 0     0 1   my ($self, $env) = @_;
71              
72 0 0         unless ($env->{'psgi.streaming'}) {
73 0           die "Plack::App::Proxy only runs with the server with psgi.streaming support";
74             }
75              
76 0 0         my $url = $self->build_url_from_env($env)
77             or return [502, ["Content-Type","text/html"], ["Can't determine proxy remote URL"]];
78              
79 0           my $req = Plack::Request->new($env);
80 0           my $headers = $self->build_headers_from_env($env, $req);
81              
82 0           my $method = $env->{REQUEST_METHOD};
83 0           my $content = $req->content;
84              
85 0           my $backend_class = Plack::Util::load_class(
86             $self->backend, 'Plack::App::Proxy::Backend'
87             );
88              
89             return $backend_class->new(
90             url => $url,
91             req => $req,
92             headers => $headers,
93             method => $method,
94             content => $content,
95             options => $self->options,
96 0     0     response_headers => sub { $self->response_headers(@_) },
97 0           )->call($env);
98             }
99              
100             sub response_headers {
101 0     0 0   my ($self, $headers) = @_;
102              
103 0           $self->filter_headers( $headers );
104              
105             # Remove PSGI forbidden headers
106 0           $headers->remove_header('Status');
107              
108 0           my @headers;
109 0     0     $headers->scan( sub { push @headers, @_ } );
  0            
110              
111 0           return @headers;
112             }
113              
114             1;
115              
116             __END__