diff --git a/dist.ini b/dist.ini index 532b9a4..9dc13d6 100644 --- a/dist.ini +++ b/dist.ini @@ -7,6 +7,7 @@ copyright_holder = Jesse Luehrs dist = Plack-Client [Prereqs] +Class::Load = 0 HTTP::Request = 0 Plack = 0.9910 Plack::App::Proxy = 0 diff --git a/lib/Plack/Client.pm b/lib/Plack/Client.pm index 588ef69..d05293b 100644 --- a/lib/Plack/Client.pm +++ b/lib/Plack/Client.pm @@ -3,10 +3,10 @@ use strict; use warnings; # ABSTRACT: abstract interface to remote web servers and local PSGI apps +use Class::Load; use HTTP::Message::PSGI; use HTTP::Request; -use Plack::App::Proxy; -use Plack::Middleware::ContentLength; +use Plack::Request; use Plack::Response; use Scalar::Util qw(blessed reftype); @@ -76,39 +76,44 @@ sub new { my $class = shift; my %params = @_; - die 'apps must be a hashref' - if exists($params{apps}) && ref($params{apps}) ne 'HASH'; - bless { - apps => $params{apps}, - proxy => Plack::App::Proxy->new->to_app, + backend => {}, + backend_args => \%params, }, $class; } -=method apps - - my $apps = $client->apps; - -Returns the C hashref that was passed to the constructor. +=method backend =cut -sub apps { shift->{apps} } -sub _proxy { shift->{proxy} } +sub backend { + my $self = shift; + my ($scheme) = @_; + $scheme = $self->_normalize_scheme($scheme); + return $self->{backend}->{$scheme}; +} -=method app_for +sub _set_backend { + my $self = shift; + my ($scheme, $backend) = @_; + $scheme = $self->_normalize_scheme($scheme); + $self->{backend}->{$scheme} = $backend; +} - my $app = $client->app_for('foo'); +sub _normalize_scheme { + my $self = shift; -Returns the app corresponding to the given app name (or undef, if no such app -exists). + my $scheme = blessed($_[0]) ? $_[0]->scheme : $_[0]; + $scheme =~ s/-ssl$//; + $scheme = 'http' if $scheme eq 'https'; -=cut + return $scheme; +} -sub app_for { +sub _backend_args { my $self = shift; - my ($for) = @_; - return $self->apps->{$for}; + my ($scheme) = @_; + return %{ $self->{backend_args}->{$scheme} || {} }; } =method request @@ -196,25 +201,21 @@ sub _http_request_to_env { my $self = shift; my ($req) = @_; - my $scheme = $req->uri->scheme; - my $app_name; + my $scheme = $req->uri->scheme; + my $authority = $req->uri->authority; + # hack around with this - psgi requires a host and port to exist, and # for the scheme to be either http or https - if ($scheme eq 'psgi-local') { - $app_name = $req->uri->authority; - $req->uri->scheme('http'); - $req->uri->host('Plack::Client'); - $req->uri->port(-1); - } - elsif ($scheme eq 'psgi-local-ssl') { - $app_name = $req->uri->authority; - $req->uri->scheme('https'); + if ($scheme ne 'http' && $scheme ne 'https') { + if ($scheme =~ /-ssl$/) { + $req->uri->scheme('https'); + } + else { + $req->uri->scheme('http'); + } $req->uri->host('Plack::Client'); $req->uri->port(-1); } - elsif ($scheme ne 'http' && $scheme ne 'https') { - die 'Invalid URL scheme ' . $scheme; - } # work around http::message::psgi bug - see github issue 163 for plack if (!$req->uri->path) { @@ -227,8 +228,8 @@ sub _http_request_to_env { $env->{CONTENT_LENGTH} ||= length($req->content); $env->{'plack.client.url_scheme'} = $scheme; - $env->{'plack.client.app_name'} = $app_name - if defined $app_name; + $env->{'plack.client.authority'} = $authority + if defined $authority; return $env; } @@ -239,30 +240,34 @@ sub _app_from_req { my $uri = $req->uri; my $scheme = $req->env->{'plack.client.url_scheme'} || $uri->scheme; - my $app_name = $req->env->{'plack.client.app_name'}; - my $app; - if ($scheme eq 'psgi-local') { - if (!defined $app_name) { - $app_name = $uri->authority; - $app_name =~ s/(.*):.*/$1/; # in case a port was added at some point - } - $app = $self->app_for($app_name); - die "Unknown app: $app_name" unless $app; - $app = Plack::Middleware::ContentLength->wrap($app); - } - elsif ($scheme eq 'http' || $scheme eq 'https') { - my $uri = $uri->clone; - $uri->path('/'); - $req->env->{'plack.proxy.remote'} = $uri->as_string; - $app = $self->_proxy; - } + my $backend = $self->_scheme_to_backend($scheme); + my $app = $backend->app_from_req($req); die "Couldn't find app" unless $app; return $app; } +sub _scheme_to_backend { + my $self = shift; + my ($scheme) = @_; + + $scheme = $self->_normalize_scheme($scheme); + + my $backend = $self->backend($scheme); + return $backend if $backend; + + (my $scheme_class = $scheme) =~ s/-/_/; + $scheme_class = "Plack::Client::Backend::$scheme_class"; + Class::Load::load_class($scheme_class); + + $backend = $scheme_class->new($self->_backend_args($scheme)); + $self->_set_backend($scheme, $backend); + + return $self->backend($scheme); +} + sub _resolve_response { my $self = shift; my ($psgi_res) = @_; diff --git a/lib/Plack/Client/Backend/http.pm b/lib/Plack/Client/Backend/http.pm new file mode 100644 index 0000000..9982cd1 --- /dev/null +++ b/lib/Plack/Client/Backend/http.pm @@ -0,0 +1,28 @@ +package Plack::Client::Backend::http; +use strict; +use warnings; + +use Plack::App::Proxy; + +sub new { + my $class = shift; + my %params = @_; + + bless { + proxy => Plack::App::Proxy->new->to_app, + }, $class; +} + +sub proxy { shift->{proxy} } + +sub app_from_req { + my $self = shift; + my ($req) = @_; + + my $uri = $req->uri->clone; + $uri->path('/'); + $req->env->{'plack.proxy.remote'} = $uri->as_string; + return $self->proxy; +} + +1; diff --git a/lib/Plack/Client/Backend/psgi_local.pm b/lib/Plack/Client/Backend/psgi_local.pm new file mode 100644 index 0000000..212479e --- /dev/null +++ b/lib/Plack/Client/Backend/psgi_local.pm @@ -0,0 +1,41 @@ +package Plack::Client::Backend::psgi_local; +use strict; +use warnings; + +use Plack::Middleware::ContentLength; + +sub new { + my $class = shift; + my %params = @_; + + die 'apps must be a hashref' + if exists($params{apps}) && ref($params{apps}) ne 'HASH'; + + bless { + apps => $params{apps}, + }, $class; +} + +sub apps { shift->{apps} } + +sub app_for { + my $self = shift; + my ($for) = @_; + return $self->apps->{$for}; +} + +sub app_from_req { + my $self = shift; + my ($req) = @_; + + my $app_name = $req->env->{'plack.client.authority'}; + if (!defined $app_name) { + $app_name = $req->uri->authority; + $app_name =~ s/(.*):.*/$1/; # in case a port was added at some point + } + my $app = $self->app_for($app_name); + die "Unknown app: $app_name" unless $app; + return Plack::Middleware::ContentLength->wrap($app); +} + +1; diff --git a/t/01-basic.t b/t/01-basic.t index 0be56ac..6e185b4 100644 --- a/t/01-basic.t +++ b/t/01-basic.t @@ -35,11 +35,8 @@ test_tcp_plackup( ] }, }; - my $client = Plack::Client->new(apps => $apps); + my $client = Plack::Client->new('psgi-local' => {apps => $apps}); isa_ok($client, 'Plack::Client'); - is($client->apps, $apps, "got apps back"); - is($client->app_for('foo'), $apps->{foo}, "got the right app"); - is($client->app_for('bar'), undef, "didn't get nonexistent app"); { my $res = $client->get('psgi-local://foo/'); diff --git a/t/02-inputs.t b/t/02-inputs.t index 04d5312..09e471c 100644 --- a/t/02-inputs.t +++ b/t/02-inputs.t @@ -48,7 +48,10 @@ test_tcp_plackup( }; my $base_uri = 'psgi-local://foo'; - test_responses($base_uri, Plack::Client->new(apps => $apps)); + test_responses( + $base_uri, + Plack::Client->new('psgi-local' => {apps => $apps}) + ); } sub test_responses { diff --git a/t/03-delayed-response.t b/t/03-delayed-response.t index 9d9b258..26d9037 100644 --- a/t/03-delayed-response.t +++ b/t/03-delayed-response.t @@ -51,7 +51,10 @@ test_tcp_plackup( }; my $base_uri = 'psgi-local://foo'; - test_responses($base_uri, Plack::Client->new(apps => $apps)); + test_responses( + $base_uri, + Plack::Client->new('psgi-local' => {apps => $apps}) + ); } sub test_responses { diff --git a/t/04-streaming.t b/t/04-streaming.t index a494681..17a42e6 100644 --- a/t/04-streaming.t +++ b/t/04-streaming.t @@ -52,7 +52,10 @@ test_tcp_plackup( }; my $base_uri = 'psgi-local://foo'; - test_responses($base_uri, Plack::Client->new(apps => $apps)); + test_responses( + $base_uri, + Plack::Client->new('psgi-local' => {apps => $apps}) + ); } sub test_responses {