Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(Rebased) Pr/behind proxy middleware #1660

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cpanfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ requires 'Path::Tiny';
requires 'Plack', '1.0040';
requires 'Plack::Middleware::FixMissingBodyInRedirect';
requires 'Plack::Middleware::RemoveRedundantBody';
requires 'Plack::Middleware::ReverseProxy';
requires 'Plack::Middleware::ReverseProxyPath';
requires 'POSIX';
requires 'Ref::Util';
requires 'Safe::Isa';
Expand Down
19 changes: 12 additions & 7 deletions lib/Dancer2/Core/App.pm
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use Plack::Middleware::FixMissingBodyInRedirect;
use Plack::Middleware::Head;
use Plack::Middleware::Conditional;
use Plack::Middleware::ConditionalGET;
use Dancer2::Middleware::BehindProxy;

use Dancer2::FileUtils 'path';
use Dancer2::Core;
Expand Down Expand Up @@ -1419,10 +1420,15 @@ sub to_app {

# Wrap with common middleware
if ( ! $self->config->{'no_default_middleware'} ) {
# BehindProxy (this is not runtime configurable)
$self->config->{'behind_proxy'}
and $psgi = Dancer2::Middleware::BehindProxy->wrap($psgi);

# FixMissingBodyInRedirect
$psgi = Plack::Middleware::FixMissingBodyInRedirect->wrap( $psgi );
$psgi = Plack::Middleware::FixMissingBodyInRedirect->wrap($psgi);

# Apply Head. After static so a HEAD request on static content DWIM.
$psgi = Plack::Middleware::Head->wrap( $psgi );
$psgi = Plack::Middleware::Head->wrap($psgi);
}

return $psgi;
Expand Down Expand Up @@ -1587,12 +1593,11 @@ sub build_request {

# If we have an app, send the serialization engine
my $request = Dancer2::Core::Request->new(
env => $env,
is_behind_proxy => $self->settings->{'behind_proxy'} || 0,
env => $env,

$self->has_serializer_engine
? ( serializer => $self->serializer_engine )
: (),
$self->has_serializer_engine
? ( serializer => $self->serializer_engine )
: (),
);

return $request;
Expand Down
39 changes: 7 additions & 32 deletions lib/Dancer2/Core/Request.pm
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ our $XS_HTTP_COOKIES = eval { require_module('HTTP::XSCookies'); 1; };

our $_id = 0;

# self->new( env => {}, serializer => $s, is_behind_proxy => 0|1 )
# self->new( env => {}, serializer => $s )
sub new {
my ( $class, @args ) = @_;

Expand All @@ -65,9 +65,8 @@ sub new {
}

# additionally supported attributes
$self->{'id'} = ++$_id;
$self->{'vars'} = {};
$self->{'is_behind_proxy'} = !!$opts{'is_behind_proxy'};
$self->{'id'} = ++$_id;
$self->{'vars'} = {};

$opts{'body_params'}
and $self->{'_body_params'} = $opts{'body_params'};
Expand Down Expand Up @@ -130,40 +129,17 @@ sub _set_route_params {
# XXX: incompatible with Plack::Request
sub uploads { $_[0]->{'uploads'} }

sub is_behind_proxy { $_[0]->{'is_behind_proxy'} || 0 }

sub host {
my ($self) = @_;

if ( $self->is_behind_proxy and exists $self->env->{'HTTP_X_FORWARDED_HOST'} ) {
my @hosts = split /\s*,\s*/, $self->env->{'HTTP_X_FORWARDED_HOST'}, 2;
return $hosts[0];
} else {
return $self->env->{'HTTP_HOST'};
}
return shift->env->{'HTTP_HOST'};
}

# aliases, kept for backward compat
sub agent { shift->user_agent }
sub remote_address { shift->address }

sub forwarded_for_address { shift->env->{'HTTP_X_FORWARDED_FOR'} }
sub forwarded_host { shift->env->{'HTTP_X_FORWARDED_HOST'} }

# there are two options
sub forwarded_protocol {
$_[0]->env->{'HTTP_X_FORWARDED_PROTO'} ||
$_[0]->env->{'HTTP_X_FORWARDED_PROTOCOL'} ||
$_[0]->env->{'HTTP_FORWARDED_PROTO'}
}

sub scheme {
my ($self) = @_;
my $scheme = $self->is_behind_proxy
? $self->forwarded_protocol
: '';

return $scheme || $self->env->{'psgi.url_scheme'};
}
sub forwarded_protocol { shift->env->{'HTTP_X_FORWARDED_PROTO'} }

sub serializer { $_[0]->{'serializer'} }

Expand Down Expand Up @@ -589,8 +565,7 @@ sub _shallow_clone {
$new_request->{headers} = $self->headers;

# Copy remaining settings
$new_request->{is_behind_proxy} = $self->{is_behind_proxy};
$new_request->{vars} = $self->{vars};
$new_request->{vars} = $self->{vars};

# Clone any existing decoded & cached body params. (GH#1116 GH#1269)
$new_request->{'body_parameters'} = $self->body_parameters->clone;
Expand Down
1 change: 0 additions & 1 deletion lib/Dancer2/Core/Runner.pm
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use Module::Runtime 'require_module';
use Dancer2::Core::MIME;
use Dancer2::Core::Types;
use Dancer2::Core::Dispatcher;
use Plack::Builder qw();
use Ref::Util qw< is_ref is_regexpref >;

# Hashref of configurable items for the runner.
Expand Down
58 changes: 58 additions & 0 deletions lib/Dancer2/Middleware/BehindProxy.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package Dancer2::Middleware::BehindProxy;
# ABSTRACT: Support Dancer2 apps when operating behing a reverse proxy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: behind


use warnings;
use strict;

use parent 'Plack::Middleware';
use Plack::Middleware::ReverseProxy;
use Plack::Middleware::ReverseProxyPath;

sub call {
my($self, $env) = @_;

# Plack::Middleware::ReverseProxy only supports
# HTTP_X_FORWARDED_PROTO whereas Dancer2 also supports
# HTTP_X_FORWARDED_PROTOCOL and HTTP_FORWARDED_PROTO
for my $header (qw/HTTP_X_FORWARDED_PROTOCOL HTTP_FORWARDED_PROTO/) {
if ( ! $env->{HTTP_X_FORWARDED_PROTO}
&& $env->{$header} )
{
$env->{HTTP_X_FORWARDED_PROTO} = $env->{$header};
last;
}
}

# Pr#503 added support for HTTP_X_FORWARDED_HOST containing multiple
# values. Plack::Middleware::ReverseProxy takes the last (most recent)
# whereas that #503 takes the first.
if ( $env->{HTTP_X_FORWARDED_HOST} ) {
my @hosts = split /\s*,\s*/, $env->{HTTP_X_FORWARDED_HOST}, 2;
$env->{HTTP_X_FORWARDED_HOST} = $hosts[0];
}

# Plack::Middleware::ReverseProxyPath uses X-Forwarded-Script-Name
# whereas Dancer previously supported HTTP_REQUEST_BASE
if ( ! $env->{HTTP_X_FORWARDED_SCRIPT_NAME}
&& $env->{HTTP_REQUEST_BASE} )
{
$env->{HTTP_X_FORWARDED_SCRIPT_NAME} = $env->{HTTP_REQUEST_BASE};
}

# Wrap in reverse proxy middleware and call the wrapped app
my $app = Plack::Middleware::ReverseProxyPath->wrap($self->app);
$app = Plack::Middleware::ReverseProxy->wrap($app);
return $app->($env);
}

1;

__END__

=head1 DESCRIPTION

Modifies request headers supported by L<Dancer2> altered by reverse proxies before
wraping the request in the commonly used reverse proxy PSGI middlewares;
L<Plack::Middleware::ReverseProxy> and L<Plack::Middleware::ReverseProxyPath>.

=cut
2 changes: 1 addition & 1 deletion t/issues/gh-730.t
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use HTTP::Request::Common;
package App;
use Dancer2;

get '/' => sub { request->is_behind_proxy };
get '/' => sub { app->config->{'behind_proxy'} };
}

my $app = App->to_app;
Expand Down
25 changes: 0 additions & 25 deletions t/request.t
Original file line number Diff line number Diff line change
Expand Up @@ -97,31 +97,6 @@ sub run_test {
is $req->base, 'http://oddhostname:5000/foo';
}

note "testing behind proxy"; {
my $req = Dancer2::Core::Request->new(
env => $env,
is_behind_proxy => 1
);
is $req->secure, 1;
is $req->host, $env->{HTTP_X_FORWARDED_HOST};
is $req->scheme, 'https';
}

note "testing behind proxy when optional headers are not set"; {
# local modifications to env:
local $env->{HTTP_HOST} = 'oddhostname:5000';
delete local $env->{'HTTP_X_FORWARDED_FOR'};
delete local $env->{'HTTP_X_FORWARDED_HOST'};
delete local $env->{'HTTP_X_FORWARDED_PROTOCOL'};
my $req = Dancer2::Core::Request->new(
env => $env,
is_behind_proxy => 1
);
is ! $req->secure, 1;
is $req->host, 'oddhostname:5000';
is $req->scheme, 'http';
}

note "testing path and uri_base"; {
# Base env used for path and uri_base tests
my $base = {
Expand Down