From b974736ef87a248b8caa11276de5e4b7fc5e3833 Mon Sep 17 00:00:00 2001 From: Charles McGarvey Date: Sun, 14 Apr 2013 12:40:12 -0600 Subject: [PATCH] use super build_headers_from_env; bump version --- dist.ini | 2 +- lib/Plack/App/Proxy/WebSocket.pm | 90 +++++++++++++++++++++++--------- 2 files changed, 67 insertions(+), 25 deletions(-) diff --git a/dist.ini b/dist.ini index 0c592c9..72a709d 100644 --- a/dist.ini +++ b/dist.ini @@ -1,6 +1,6 @@ name = Plack-App-Proxy-WebSocket -version = 0.01 +version = 0.02 author = Charles McGarvey license = Perl_5 copyright_holder = Charles McGarvey diff --git a/lib/Plack/App/Proxy/WebSocket.pm b/lib/Plack/App/Proxy/WebSocket.pm index 70b4d4f..770bae8 100644 --- a/lib/Plack/App/Proxy/WebSocket.pm +++ b/lib/Plack/App/Proxy/WebSocket.pm @@ -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 that adds support for proxying -WebSocket connections. It has no extra dependencies or configuration options -beyond what L 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 server features in order to -work. The server must support C and C. It is also -highly recommended to choose a C server, though that isn't -strictly required; performance may suffer greatly without it. L is an -excellent choice for this application. +WebSocket connections. It works by looking for the C 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 as usual, though there are a few differences +related to the generation of headers for the back-end request; see +L for details. + +This module has no configuration options beyond what L +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 server features are required in order for the WebSocket +proxying to work. The server must support C and C. +It is also highly recommended that you choose a C server, +though that isn't strictly required. L is one good choice for this +application. This module is B. 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) ignore the C HTTP response header -and use their own values, but WebSocket clients expect the value of that -header to be C. This module cannot work on such servers. Your best -bet is to use a non-blocking server like L that doesn't mess with the -C header. +L ignores the C HTTP response header from applications +and chooses its own value (C or C), but WebSocket clients +expect the value of that header to be C. Therefore, WebSocket +proxying does not work on L. Your best bet is to use a server that +doesn't mess with the C header, like L. =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 to maintain +the complete list of proxies in C and to set the following +headers if they are not already set: C to the value of +C, C to the value of C, and C +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; -- 2.45.2