use super build_headers_from_env; bump version v0.02
authorCharles McGarvey <chazmcgarvey@brokenzipper.com>
Sun, 14 Apr 2013 18:40:12 +0000 (12:40 -0600)
committerCharles McGarvey <chazmcgarvey@brokenzipper.com>
Mon, 15 Apr 2013 03:59:54 +0000 (21:59 -0600)
dist.ini
lib/Plack/App/Proxy/WebSocket.pm

index 0c592c9fadb96111247c21cbd347f385c35f9260..72a709dbe7e61e122001d081bce358f5b6706fc6 100644 (file)
--- a/dist.ini
+++ b/dist.ini
@@ -1,6 +1,6 @@
 
 name = Plack-App-Proxy-WebSocket
-version = 0.01
+version = 0.02
 author = Charles McGarvey <ccm@cpan.org>
 license = Perl_5
 copyright_holder = Charles McGarvey
index 70b4d4f1ac3908365cca7cf2cce4c065b116e56b..770bae8cfdc0d9a8b8a520b6bd1d96aa244ca617 100644 (file)
@@ -6,8 +6,9 @@ use strict;
 
 use AnyEvent::Handle;
 use AnyEvent::Socket;
-use HTTP::Parser::XS qw/parse_http_response HEADERS_AS_ARRAYREF/;
+use HTTP::Headers;
 use HTTP::Request;
+use HTTP::Parser::XS qw/parse_http_response HEADERS_AS_ARRAYREF/;
 use Plack::Request;
 use URI;
 
@@ -28,27 +29,32 @@ use parent 'Plack::App::Proxy';
 =head1 DESCRIPTION
 
 This is a subclass of L<Plack::App::Proxy> that adds support for proxying
-WebSocket connections.  It has no extra dependencies or configuration options
-beyond what L<Plack::App::Proxy> requires or provides, so it may be an easy
-drop-in replacement.  Read the documentation of that module for advanced usage
-not covered here.
-
-This subclass necessarily requires extra L<PSGI> server features in order to
-work.  The server must support C<psgi.streaming> and C<psgix.io>.  It is also
-highly recommended to choose a C<psgi.nonblocking> server, though that isn't
-strictly required; performance may suffer greatly without it.  L<Twiggy> is an
-excellent choice for this application.
+WebSocket connections.  It works by looking for the C<Upgrade> header,
+forwarding the handshake to the remote back-end, and then buffering
+full-duplex between the client and the remote.  Regular HTTP requests are
+handled by L<Plack::App::Proxy> as usual, though there are a few differences
+related to the generation of headers for the back-end request; see
+L</build_headers_from_env> for details.
+
+This module has no configuration options beyond what L<Plack::App::Proxy>
+requires or provides, so it may be an easy drop-in replacement.  Read the
+documentation of that module for advanced usage not covered here.  Also note
+that extra L<PSGI> server features are required in order for the WebSocket
+proxying to work.  The server must support C<psgi.streaming> and C<psgix.io>.
+It is also highly recommended that you choose a C<psgi.nonblocking> server,
+though that isn't strictly required.  L<Twiggy> is one good choice for this
+application.
 
 This module is B<EXPERIMENTAL>.  I use it in development and it works
 swimmingly for me, but it is completely untested in production scenarios.
 
 =head1 CAVEATS
 
-Some servers (e.g. L<Starman>) ignore the C<Connection> HTTP response header
-and use their own values, but WebSocket clients expect the value of that
-header to be C<Upgrade>.  This module cannot work on such servers.  Your best
-bet is to use a non-blocking server like L<Twiggy> that doesn't mess with the
-C<Connection> header.
+L<Starman> ignores the C<Connection> HTTP response header from applications
+and chooses its own value (C<Close> or C<Keep-Alive>), but WebSocket clients
+expect the value of that header to be C<Upgrade>.  Therefore, WebSocket
+proxying does not work on L<Starman>.  Your best bet is to use a server that
+doesn't mess with the C<Connection> header, like L<Twiggy>.
 
 =cut
 
@@ -56,8 +62,8 @@ sub call {
     my ($self, $env) = @_;
     my $req = Plack::Request->new($env);
 
-    # detect the websocket handshake or just proxy as usual
-    lc($req->header('Upgrade') || "") eq 'websocket' or return $self->SUPER::call($env);
+    # detect a protocol upgrade handshake or just proxy as usual
+    my $upgrade = $req->header('Upgrade') or return $self->SUPER::call($env);
 
     $env->{'psgi.streaming'} or die "Plack server support for psgi.streaming is required";
     my $client_fh = $env->{'psgix.io'} or die "Plack server support for the psgix.io extension is required";
@@ -88,11 +94,11 @@ sub call {
             my $client = AnyEvent::Handle->new(fh => $client_fh);
             my $server = AnyEvent::Handle->new(fh => $server_fh);
 
-            # forward request from the client, modifying the host and origin
-            my $headers = $req->headers->clone;
-            my $host = $uri->host_port;
-            $headers->header(Host => $host, Origin => "http://$host");
-            my $hs = HTTP::Request->new('GET', $uri->path, $headers);
+            # forward request from the client
+            my $headers = $self->build_headers_from_env($env, $req, $uri);
+            $headers->{Upgrade} = $upgrade;
+            $headers->{Connection} = 'Upgrade';
+            my $hs = HTTP::Request->new('GET', $uri->path, HTTP::Headers->new(%$headers));
             $hs->protocol($req->protocol);
             $server->push_write($hs->as_string);
 
@@ -117,6 +123,7 @@ sub call {
                 $server->push_shutdown if $ret == -2;
                 return if $ret < 0;
 
+                $headers = [$self->response_headers($headers)] unless $status == 101;
                 $writer = $res->([$status, $headers]);
                 $writer->write(substr($buffer, $ret));
                 $buffer = undef;
@@ -138,6 +145,41 @@ sub call {
 
         $cv->recv if $cv;
     };
-};
+}
+
+=method build_headers_from_env
+
+Supplement the headers-building logic from L<Plack::App::Proxy> to maintain
+the complete list of proxies in C<X-Forwarded-For> and to set the following
+headers if they are not already set: C<X-Forwarded-Proto> to the value of
+C<psgi.url_scheme>, C<X-Real-IP> to the value of C<REMOTE_ADDR>, and C<Host>
+to the host and port number of a URI (if given).
+
+This is called internally.
+
+=cut
+
+sub build_headers_from_env {
+    my ($self, $env, $req, $uri) = @_;
+
+    my $headers = $self->SUPER::build_headers_from_env($env, $req);
+
+    # if x-forwarded-for already existed, append the remote address; the super
+    # method fails to maintain a list of mutiple proxies
+    if (my $forwarded_for = $env->{HTTP_X_FORWARDED_FOR}) {
+        $headers->{'X-Forwarded-For'} = "$forwarded_for, $env->{REMOTE_ADDR}";
+    }
+
+    # the super method depends on the user agent to add the host header if it
+    # is missing, so set the host if it needs to be set
+    if ($uri && !$headers->{'Host'}) {
+        $headers->{'Host'} = $uri->host_port;
+    }
+
+    $headers->{'X-Forwarded-Proto'} ||= $env->{'psgi.url_scheme'};
+    $headers->{'X-Real-IP'} ||= $env->{REMOTE_ADDR};
+
+    $headers;
+}
 
 1;
This page took 0.026292 seconds and 4 git commands to generate.