Skip to content

Commit

Permalink
support http+unix:// for proxy servers too
Browse files Browse the repository at this point in the history
  • Loading branch information
kraih committed Feb 24, 2017
1 parent db337ec commit f8a0eaf
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 263 deletions.
2 changes: 1 addition & 1 deletion lib/Mojo/Message/Request.pm
Expand Up @@ -219,7 +219,7 @@ sub _start_line {
# CONNECT
my $method = uc $self->method;
if ($method eq 'CONNECT') {
my $port = $url->port || ($url->protocol eq 'https' ? '443' : '80');
my $port = $url->port // ($url->protocol eq 'https' ? '443' : '80');
$path = $url->ihost . ":$port";
}

Expand Down
2 changes: 1 addition & 1 deletion lib/Mojo/URL.pm
Expand Up @@ -28,7 +28,7 @@ sub host_port {
}

return undef unless defined(my $host = $self->ihost);
return $host unless my $port = $self->port;
return $host unless defined(my $port = $self->port);
return "$host:$port";
}

Expand Down
6 changes: 5 additions & 1 deletion lib/Mojo/UserAgent.pm
Expand Up @@ -606,12 +606,16 @@ Proxy manager, defaults to a L<Mojo::UserAgent::Proxy> object.
# Detect proxy servers from environment
$ua->proxy->detect;
# Manually configure HTTP proxy (using CONNECT for HTTPS)
# Manually configure HTTP proxy (using CONNECT for HTTPS/WebSockets)
$ua->proxy->http('http://127.0.0.1:8080')->https('http://127.0.0.1:8080');
# Manually configure Tor (SOCKS5)
$ua->proxy->http('socks://127.0.0.1:9050')->https('socks://127.0.0.1:9050');
# Manually configure UNIX domain socket (using CONNECT for HTTPS/WebSockets)
$ua->proxy->http('http+unix://%2Ftmp%2Fproxy.sock')
->https('http+unix://%2Ftmp%2Fproxy.sock');
=head2 request_timeout
my $timeout = $ua->request_timeout;
Expand Down
4 changes: 2 additions & 2 deletions lib/Mojo/UserAgent/Transactor.pm
Expand Up @@ -27,7 +27,7 @@ sub endpoint {
my $url = $req->url;
my $proto = $url->protocol || 'http';
my $host = $url->ihost;
my $port = $url->port || ($proto eq 'https' ? 443 : 80);
my $port = $url->port // ($proto eq 'https' ? 443 : 80);

# Proxy for normal HTTP requests
my $socks;
Expand Down Expand Up @@ -250,7 +250,7 @@ sub _proxy {
my $req = $tx->req;
if ($req->via_proxy && (my $proxy = $req->proxy)) {
return $proxy->protocol, $proxy->ihost,
$proxy->port || ($proto eq 'https' ? 443 : 80);
$proxy->port // ($proto eq 'https' ? 443 : 80);
}

return $proto, $host, $port;
Expand Down
82 changes: 9 additions & 73 deletions t/mojo/daemon_ipv6_tls.t
Expand Up @@ -5,6 +5,9 @@ BEGIN { $ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll' }
use Test::More;
use Mojo::IOLoop::TLS;

use FindBin;
use lib "$FindBin::Bin/lib";

plan skip_all => 'set TEST_IPV6 to enable this test (developer only!)'
unless $ENV{TEST_IPV6};
plan skip_all => 'set TEST_TLS to enable this test (developer only!)'
Expand All @@ -19,6 +22,7 @@ plan skip_all => 'IO::Socket::SSL 1.94+ required for this test!'
# -CAkey ca.key -CAcreateserial
use Mojo::IOLoop;
use Mojo::Server::Daemon;
use Mojo::TestConnectProxy;
use Mojo::UserAgent;
use Mojolicious::Lite;

Expand All @@ -27,77 +31,6 @@ app->log->level('fatal');

get '/' => {text => 'works!'};

# CONNECT proxy server for testing
my (%buffer, $forward);
my $id = Mojo::IOLoop->server(
{address => '[::1]'} => sub {
my ($loop, $stream, $id) = @_;

# Connection to client
$stream->on(
read => sub {
my ($stream, $chunk) = @_;

# Write chunk from client to server
my $server = $buffer{$id}{connection};
return Mojo::IOLoop->stream($server)->write($chunk) if $server;

# Read connect request from client
my $buffer = $buffer{$id}{client} .= $chunk;
if ($buffer =~ /\x0d?\x0a\x0d?\x0a$/) {
$buffer{$id}{client} = '';
if ($buffer =~ /CONNECT \S+:\d+/) {

# Connection to server
$buffer{$id}{connection} = Mojo::IOLoop->client(
{address => '[::1]', port => $forward} => sub {
my ($loop, $err, $stream) = @_;

# Connection to server failed
if ($err) {
Mojo::IOLoop->remove($id);
return delete $buffer{$id};
}

# Start forwarding data in both directions
Mojo::IOLoop->stream($id)
->write("HTTP/1.1 200 OK\x0d\x0a"
. "Connection: keep-alive\x0d\x0a\x0d\x0a");
$stream->on(
read => sub {
my ($stream, $chunk) = @_;
Mojo::IOLoop->stream($id)->write($chunk);
}
);

# Server closed connection
$stream->on(
close => sub {
Mojo::IOLoop->remove($id);
delete $buffer{$id};
}
);
}
);
}

# Invalid request from client
else { Mojo::IOLoop->remove($id) }
}
}
);

# Client closed connection
$stream->on(
close => sub {
my $buffer = delete $buffer{$id};
Mojo::IOLoop->remove($buffer->{connection}) if $buffer->{connection};
}
);
}
);
my $proxy = Mojo::IOLoop->acceptor($id)->port;

# IPv6 and TLS
my $daemon = Mojo::Server::Daemon->new(
app => app,
Expand All @@ -121,8 +54,11 @@ SKIP: {
. '&127.0.0.1_key=t/mojo/certs/server.key'
. '&example.com_cert=t/mojo/certs/domain.crt'
. '&example.com_key=t/mojo/certs/domain.key';
$forward = $daemon->listen([$listen])->start->ports->[0];
$ua = Mojo::UserAgent->new(
my $forward = $daemon->listen([$listen])->start->ports->[0];
my $id = Mojo::TestConnectProxy::proxy({address => '[::1]'},
{address => '[::1]', port => $forward});
my $proxy = Mojo::IOLoop->acceptor($id)->port;
$ua = Mojo::UserAgent->new(
ioloop => Mojo::IOLoop->singleton,
ca => 't/mojo/certs/ca.crt'
);
Expand Down
10 changes: 6 additions & 4 deletions t/mojo/file.t
Expand Up @@ -124,16 +124,18 @@ is $destination->slurp, 'works!', 'right content';
is_deeply path('does_not_exist')->list->to_array, [], 'no files';
is_deeply path(__FILE__)->list->to_array, [], 'no files';
my $lib = path(__FILE__)->dirname->child('lib', 'Mojo');
my @files = map { path($lib)->child(split '/') }
('DeprecationTest.pm', 'LoaderException.pm', 'LoaderException2.pm');
my @files = map { path($lib)->child(split '/') } (
'DeprecationTest.pm', 'LoaderException.pm',
'LoaderException2.pm', 'TestConnectProxy.pm'
);
is_deeply path($lib)->list->map('to_string')->to_array, \@files, 'right files';
unshift @files, $lib->child('.hidden.txt')->to_string;
is_deeply path($lib)->list({hidden => 1})->map('to_string')->to_array, \@files,
'right files';
@files = map { path($lib)->child(split '/') } (
'BaseTest', 'DeprecationTest.pm',
'LoaderException.pm', 'LoaderException2.pm',
'LoaderTest'
'LoaderTest', 'TestConnectProxy.pm'
);
is_deeply path($lib)->list({dir => 1})->map('to_string')->to_array, \@files,
'right files';
Expand All @@ -149,7 +151,7 @@ is_deeply path(__FILE__)->list_tree->to_array, [], 'no files';
'BaseTest/Base3.pm', 'DeprecationTest.pm',
'LoaderException.pm', 'LoaderException2.pm',
'LoaderTest/A.pm', 'LoaderTest/B.pm',
'LoaderTest/C.pm'
'LoaderTest/C.pm', 'TestConnectProxy.pm'
);
is_deeply path($lib)->list_tree->map('to_string')->to_array, \@files,
'right files';
Expand Down
84 changes: 84 additions & 0 deletions t/mojo/lib/Mojo/TestConnectProxy.pm
@@ -0,0 +1,84 @@
package Mojo::TestConnectProxy;
use Mojo::Base -strict;

use Mojo::IOLoop;

# CONNECT proxy server for testing
sub proxy {
my ($from, $to, $ok, $zero) = @_;

$ok ||= "HTTP/1.1 200 OK\x0d\x0aConnection: keep-alive\x0d\x0a\x0d\x0a";
$zero ||= "HTTP/1.1 404 NOT FOUND\x0d\x0aContent-Length: 0\x0d\x0a"
. "Connection: close\x0d\x0a\x0d\x0a";

my %buffer;
return Mojo::IOLoop->server(
$from => sub {
my ($loop, $stream, $id) = @_;

# Connection to client
$stream->on(
read => sub {
my ($stream, $chunk) = @_;

# Write chunk from client to server
my $server = $buffer{$id}{connection};
return Mojo::IOLoop->stream($server)->write($chunk) if $server;

# Read connect request from client
my $buffer = $buffer{$id}{client} .= $chunk;
if ($buffer =~ /\x0d?\x0a\x0d?\x0a$/) {
$buffer{$id}{client} = '';
if ($buffer =~ /CONNECT \S+:(\d+)/) {

return Mojo::IOLoop->stream($id)->write($zero) if $1 == 0;

# Connection to server
$buffer{$id}{connection} = Mojo::IOLoop->client(
$to => sub {
my ($loop, $err, $stream) = @_;

# Connection to server failed
if ($err) {
Mojo::IOLoop->remove($id);
return delete $buffer{$id};
}

# Start forwarding data in both directions
Mojo::IOLoop->stream($id)->write($ok);
$stream->on(
read => sub {
my ($stream, $chunk) = @_;
Mojo::IOLoop->stream($id)->write($chunk);
}
);

# Server closed connection
$stream->on(
close => sub {
Mojo::IOLoop->remove($id);
delete $buffer{$id};
}
);
}
);
}

# Invalid request from client
else { Mojo::IOLoop->remove($id) }
}
}
);

# Client closed connection
$stream->on(
close => sub {
my $buffer = delete $buffer{$id};
Mojo::IOLoop->remove($buffer->{connection}) if $buffer->{connection};
}
);
}
);
}

1;
40 changes: 37 additions & 3 deletions t/mojo/daemon_unix.t → t/mojo/user_agent_unix.t
Expand Up @@ -6,6 +6,9 @@ use Test::More;
use Mojo::File 'tempdir';
use IO::Socket::UNIX;

use FindBin;
use lib "$FindBin::Bin/lib";

plan skip_all => 'set TEST_UNIX to enable this test (developer only!)'
unless $ENV{TEST_UNIX};
my $dir = tempdir;
Expand All @@ -14,6 +17,7 @@ plan skip_all => 'UNIX domain socket support required for this test!'
unless IO::Socket::UNIX->new(Listen => 1, Local => $dummy);

use Mojo::Server::Daemon;
use Mojo::TestConnectProxy;
use Mojo::UserAgent;
use Mojo::Util 'url_escape';
use Mojolicious::Lite;
Expand All @@ -37,7 +41,8 @@ get '/info' => sub {

websocket '/echo' => sub {
my $c = shift;
$c->on(message => sub { shift->send(shift)->finish });
$c->on(message =>
sub { shift->send($c->req->url->to_abs->host . ': ' . shift)->finish });
};

# UNIX domain socket server
Expand Down Expand Up @@ -81,7 +86,7 @@ $ua->websocket(
}
);
Mojo::IOLoop->start;
is $result, 'roundtrip works!', 'right result';
is $result, "$test: roundtrip works!", 'right result';

# WebSocket again
$result = undef;
Expand All @@ -94,7 +99,36 @@ $ua->websocket(
}
);
Mojo::IOLoop->start;
is $result, 'roundtrip works!', 'right result';
is $result, "$test: roundtrip works!", 'right result';

# WebSocket with proxy
my $proxy = $dir->child('proxy.sock');
my $encoded_proxy = url_escape $proxy;
my $id = Mojo::TestConnectProxy::proxy({path => "$proxy"}, {path => "$test"});
$result = undef;
$ua->proxy->http("http+unix://$encoded_proxy");
$ua->websocket(
'ws://example.com/echo' => sub {
my ($ua, $tx) = @_;
$tx->on(finish => sub { Mojo::IOLoop->stop });
$tx->on(message => sub { shift->finish; $result = shift });
$tx->send('roundtrip works!');
}
);
Mojo::IOLoop->start;
is $result, 'example.com: roundtrip works!', 'right result';
Mojo::IOLoop->remove($id);

# Proxy
$ua->proxy->http("http+unix://$encoded");
$tx = $ua->get('http://example.com');
ok !$tx->kept_alive, 'connection was not kept alive';
is $tx->res->code, 200, 'right status';
is $tx->res->body, 'http://example.com', 'right content';
$tx = $ua->get('http://example.com');
ok $tx->kept_alive, 'connection was kept alive';
is $tx->res->code, 200, 'right status';
is $tx->res->body, 'http://example.com', 'right content';

# Cleanup
undef $daemon;
Expand Down

0 comments on commit f8a0eaf

Please sign in to comment.