Skip to content

Instantly share code, notes, and snippets.

Created July 28, 2014 22:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/7cd45b5399922daf695e to your computer and use it in GitHub Desktop.
Save anonymous/7cd45b5399922daf695e to your computer and use it in GitHub Desktop.
diff --git a/lib/Mojo/IOLoop.pm b/lib/Mojo/IOLoop.pm
index 9aafea0..18e7726 100644
--- a/lib/Mojo/IOLoop.pm
+++ b/lib/Mojo/IOLoop.pm
@@ -320,11 +320,12 @@ right in, to make writing test servers as easy as possible
convenience the C<PIPE> signal will be set to C<IGNORE> when L<Mojo::IOLoop>
is loaded.
-For better scalability (epoll, kqueue) and to provide IPv6 as well as TLS
-support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+) and
-L<IO::Socket::SSL> (1.84+) will be used automatically if they are installed.
-Individual features can also be disabled with the C<MOJO_NO_IPV6> and
-C<MOJO_NO_TLS> environment variables.
+For better scalability (epoll, kqueue) and to provide IPv6, SOCKS as well as
+TLS support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+),
+L<IO::Socket::Socks> (0.63+) and L<IO::Socket::SSL> (1.84+) will be used
+automatically if they are installed. Individual features can also be disabled
+with the C<MOJO_NO_IPV6>, C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment
+variables.
See L<Mojolicious::Guides::Cookbook/"REAL-TIME WEB"> for more.
diff --git a/lib/Mojo/IOLoop/Client.pm b/lib/Mojo/IOLoop/Client.pm
index 01f79d1..7cac13b 100644
--- a/lib/Mojo/IOLoop/Client.pm
+++ b/lib/Mojo/IOLoop/Client.pm
@@ -19,6 +19,13 @@ use constant TLS => $ENV{MOJO_NO_TLS}
use constant TLS_READ => TLS ? IO::Socket::SSL::SSL_WANT_READ() : 0;
use constant TLS_WRITE => TLS ? IO::Socket::SSL::SSL_WANT_WRITE() : 0;
+# SOCKS support requires IO::Socket::Socks
+use constant SOCKS => $ENV{MOJO_NO_SOCKS}
+ ? 0
+ : eval 'use IO::Socket::Socks 0.63 (); 1';
+use constant SOCKS_READ => SOCKS ? IO::Socket::Socks::SOCKS_WANT_READ() : 0;
+use constant SOCKS_WRITE => SOCKS ? IO::Socket::Socks::SOCKS_WANT_WRITE() : 0;
+
has reactor => sub { Mojo::IOLoop->singleton->reactor };
sub DESTROY { shift->_cleanup }
@@ -42,12 +49,13 @@ sub _connect {
my $handle;
my $reactor = $self->reactor;
- my $address = $args->{address} ||= 'localhost';
+ my $address = $args->{socks_address} || ($args->{address} ||= 'localhost');
+ my $port = $args->{socks_port} || $args->{port} || ($args->{tls} ? 443 : 80);
unless ($handle = $self->{handle} = $args->{handle}) {
my %options = (
Blocking => 0,
PeerAddr => $address eq 'localhost' ? '127.0.0.1' : $address,
- PeerPort => $args->{port} || ($args->{tls} ? 443 : 80)
+ PeerPort => $port
);
$options{LocalAddr} = $args->{local_address} if $args->{local_address};
$options{PeerAddr} =~ s/[\[\]]//g if $options{PeerAddr};
@@ -63,7 +71,37 @@ sub _connect {
# Wait for handle to become writable
weaken $self;
- $reactor->io($handle => sub { $self->_try($args) })->watch($handle, 0, 1);
+ $reactor->io($handle => sub { $self->_ready($args) })->watch($handle, 0, 1);
+}
+
+sub _ready {
+ my ($self, $args) = @_;
+
+ # Retry or handle exceptions
+ my $handle = $self->{handle};
+ return $! == EINPROGRESS ? undef : $self->emit(error => $!)
+ if $handle->isa('IO::Socket::IP') && !$handle->connect;
+ return $self->emit(error => $! = $handle->sockopt(SO_ERROR))
+ unless $handle->connected;
+
+ # Disable Nagle's algorithm
+ setsockopt $handle, IPPROTO_TCP, TCP_NODELAY, 1;
+
+ $self->_try_socks($args);
+}
+
+sub _socks {
+ my ($self, $args) = @_;
+
+ # Connected
+ my $handle = $self->{handle};
+ return $self->_try_tls($args) if $handle->ready;
+
+ # Switch between reading and writing
+ my $err = $IO::Socket::Socks::SOCKS_ERROR;
+ if ($err == SOCKS_READ) { $self->reactor->watch($handle, 1, 0) }
+ elsif ($err == SOCKS_WRITE) { $self->reactor->watch($handle, 1, 1) }
+ else { $self->emit(error => $err) }
}
sub _tls {
@@ -80,19 +118,28 @@ sub _tls {
elsif ($err == TLS_WRITE) { $self->reactor->watch($handle, 1, 1) }
}
-sub _try {
+sub _try_socks {
my ($self, $args) = @_;
- # Retry or handle exceptions
my $handle = $self->{handle};
- return $! == EINPROGRESS ? undef : $self->emit(error => $!)
- if $handle->isa('IO::Socket::IP') && !$handle->connect;
- return $self->emit(error => $! = $handle->sockopt(SO_ERROR))
- unless $handle->connected;
+ return $self->_try_tls($args) unless $args->{socks_address};
+ return $self->emit(
+ error => 'IO::Socket::Socks 0.63 required for SOCKS support')
+ unless SOCKS;
- # Disable Nagle's algorithm
- setsockopt $handle, IPPROTO_TCP, TCP_NODELAY, 1;
+ my %options
+ = (ConnectAddr => $args->{address}, ConnectPort => $args->{port});
+ my $reactor = $self->reactor;
+ $reactor->remove($handle);
+ return $self->emit(error => 'SOCKS upgrade failed')
+ unless IO::Socket::Socks->start_SOCKS($handle, %options);
+ $reactor->io($handle => sub { $self->_socks($args) })->watch($handle, 0, 1);
+}
+
+sub _try_tls {
+ my ($self, $args) = @_;
+ my $handle = $self->{handle};
return $self->_cleanup->emit_safe(connect => $handle)
if !$args->{tls} || $handle->isa('IO::Socket::SSL');
return $self->emit(error => 'IO::Socket::SSL 1.84 required for TLS support')
@@ -115,7 +162,7 @@ sub _try {
my $reactor = $self->reactor;
$reactor->remove($handle);
return $self->emit(error => 'TLS upgrade failed')
- unless $handle = IO::Socket::SSL->start_SSL($handle, %options);
+ unless IO::Socket::SSL->start_SSL($handle, %options);
$reactor->io($handle => sub { $self->_tls })->watch($handle, 0, 1);
}
@@ -225,6 +272,18 @@ Local address to bind to.
Port to connect to, defaults to C<80> or C<443> with C<tls> option.
+=item socks_address
+
+ socks_address => '127.0.0.1'
+
+Address or host name of SOCKS proxy server to use for connection.
+
+=item socks_port
+
+ socks_port => 9050
+
+Port of SOCKS proxy server to use for connection.
+
=item timeout
timeout => 15
diff --git a/lib/Mojo/Message/Request.pm b/lib/Mojo/Message/Request.pm
index 264fc0d..5ba8583 100644
--- a/lib/Mojo/Message/Request.pm
+++ b/lib/Mojo/Message/Request.pm
@@ -101,15 +101,15 @@ sub get_start_line_chunk {
# CONNECT
my $method = uc $self->method;
+ my $proxy = $self->proxy;
if ($method eq 'CONNECT') {
my $port = $url->port || ($url->protocol eq 'https' ? '443' : '80');
$path = $url->ihost . ":$port";
}
# Proxy
- elsif ($self->proxy) {
- $path = $url->clone->userinfo(undef)
- unless $self->is_handshake || $url->protocol eq 'https';
+ elsif ($proxy && $proxy->protocol ne 'socks' && !$self->is_handshake) {
+ $path = $url->clone->userinfo(undef) unless $url->protocol eq 'https';
}
$self->{start_buffer} = "$method $path HTTP/@{[$self->version]}\x0d\x0a";
diff --git a/lib/Mojo/Server/Daemon.pm b/lib/Mojo/Server/Daemon.pm
index 471f7f4..b05a477 100644
--- a/lib/Mojo/Server/Daemon.pm
+++ b/lib/Mojo/Server/Daemon.pm
@@ -265,11 +265,12 @@ HTTP and WebSocket server, with IPv6, TLS, Comet (long pol
connection pooling, timeout, cookie, multipart and multiple event loop
support.
-For better scalability (epoll, kqueue) and to provide IPv6 as well as TLS
-support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+) and
-L<IO::Socket::SSL> (1.84+) will be used automatically by L<Mojo::IOLoop> if
-they are installed. Individual features can also be disabled with the
-C<MOJO_NO_IPV6> and C<MOJO_NO_TLS> environment variables.
+For better scalability (epoll, kqueue) and to provide IPv6, SOCKS as well as
+TLS support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+),
+L<IO::Socket::Socks> (0.63+) and L<IO::Socket::SSL> (1.84+) will be used
+automatically if they are installed. Individual features can also be disabled
+with the C<MOJO_NO_IPV6>, C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment
+variables.
See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
diff --git a/lib/Mojo/Server/Hypnotoad.pm b/lib/Mojo/Server/Hypnotoad.pm
index df71b51..a5100ae 100644
--- a/lib/Mojo/Server/Hypnotoad.pm
+++ b/lib/Mojo/Server/Hypnotoad.pm
@@ -172,11 +172,12 @@ You can run the same command again for automatic hot deplo
This second invocation will load the application again, detect the process id
file with it, and send a L</"USR2"> signal to the already running server.
-For better scalability (epoll, kqueue) and to provide IPv6 as well as TLS
-support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+) and
-L<IO::Socket::SSL> (1.84+) will be used automatically by L<Mojo::IOLoop> if
-they are installed. Individual features can also be disabled with the
-C<MOJO_NO_IPV6> and C<MOJO_NO_TLS> environment variables.
+For better scalability (epoll, kqueue) and to provide IPv6, SOCKS as well as
+TLS support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+),
+L<IO::Socket::Socks> (0.63+) and L<IO::Socket::SSL> (1.84+) will be used
+automatically if they are installed. Individual features can also be disabled
+with the C<MOJO_NO_IPV6>, C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment
+variables.
See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
diff --git a/lib/Mojo/Server/Morbo.pm b/lib/Mojo/Server/Morbo.pm
index 6bffa34..2badabf 100644
--- a/lib/Mojo/Server/Morbo.pm
+++ b/lib/Mojo/Server/Morbo.pm
@@ -130,11 +130,12 @@ To start applications with it you can use the L<morbo> scr
$ morbo myapp.pl
Server available at http://127.0.0.1:3000.
-For better scalability (epoll, kqueue) and to provide IPv6 as well as TLS
-support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+) and
-L<IO::Socket::SSL> (1.84+) will be used automatically by L<Mojo::IOLoop> if
-they are installed. Individual features can also be disabled with the
-C<MOJO_NO_IPV6> and C<MOJO_NO_TLS> environment variables.
+For better scalability (epoll, kqueue) and to provide IPv6, SOCKS as well as
+TLS support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+),
+L<IO::Socket::Socks> (0.63+) and L<IO::Socket::SSL> (1.84+) will be used
+automatically if they are installed. Individual features can also be disabled
+with the C<MOJO_NO_IPV6>, C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment
+variables.
See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
diff --git a/lib/Mojo/Server/Prefork.pm b/lib/Mojo/Server/Prefork.pm
index 36638a2..9333e43 100644
--- a/lib/Mojo/Server/Prefork.pm
+++ b/lib/Mojo/Server/Prefork.pm
@@ -261,11 +261,12 @@ keep-alive, connection pooling, timeout, cookie, multipart
loop support. Note that the server uses signals for process management, so you
should avoid modifying signal handlers in your applications.
-For better scalability (epoll, kqueue) and to provide IPv6 as well as TLS
-support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+) and
-L<IO::Socket::SSL> (1.84+) will be used automatically by L<Mojo::IOLoop> if
-they are installed. Individual features can also be disabled with the
-C<MOJO_NO_IPV6> and C<MOJO_NO_TLS> environment variables.
+For better scalability (epoll, kqueue) and to provide IPv6, SOCKS as well as
+TLS support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+),
+L<IO::Socket::Socks> (0.63+) and L<IO::Socket::SSL> (1.84+) will be used
+automatically if they are installed. Individual features can also be disabled
+with the C<MOJO_NO_IPV6>, C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment
+variables.
See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
diff --git a/lib/Mojo/UserAgent.pm b/lib/Mojo/UserAgent.pm
index 290b28a..f7768f9 100644
--- a/lib/Mojo/UserAgent.pm
+++ b/lib/Mojo/UserAgent.pm
@@ -85,21 +85,24 @@ sub _cleanup {
}
sub _connect {
- my ($self, $nb, $proto, $host, $port, $handle, $cb) = @_;
+ my ($self, $nb, $tx, $proto, $host, $port, $handle, $cb) = @_;
+
+ my $options
+ = {address => $host, port => $port, timeout => $self->connect_timeout};
+ if (my $local = $self->local_address) { $options->{local_address} = $local }
+ $options->{handle} = $handle if $handle;
+ if ($proto eq 'socks') {
+ @$options{qw(socks_address socks_port)} = @$options{qw(address port)};
+ ($proto, @$options{qw(address port)}) = $self->transactor->endpoint($tx);
+ }
+ if ($options->{tls} = $proto eq 'https') {
+ $options->{"tls_$_"} = $self->$_ for qw(ca cert key);
+ }
weaken $self;
my $id;
return $id = $self->_loop($nb)->client(
- address => $host,
- handle => $handle,
- local_address => $self->local_address,
- port => $port,
- timeout => $self->connect_timeout,
- tls => $proto eq 'https',
- tls_ca => $self->ca,
- tls_cert => $self->cert,
- tls_key => $self->key,
- sub {
+ $options => sub {
my ($loop, $err, $stream) = @_;
# Connection error
@@ -143,7 +146,7 @@ sub _connect_proxy {
my $handle = $loop->stream($id)->steal_handle;
my $c = delete $self->{connections}{$id};
$loop->remove($id);
- $id = $self->_connect($nb, $self->transactor->endpoint($old),
+ $id = $self->_connect($nb, $old, $self->transactor->endpoint($old),
$handle, sub { shift->_start($nb, $old->connection($id), $cb) });
$self->{connections}{$id} = $c;
}
@@ -191,7 +194,7 @@ sub _connection {
# Connect
warn "-- Connect ($proto:$host:$port)\n" if DEBUG;
($proto, $host, $port) = $self->transactor->peer($tx);
- $id = $self->_connect(($nb, $proto, $host, $port, $id) => \&_connected);
+ $id = $self->_connect(($nb, $tx, $proto, $host, $port, $id) => \&_connected);
$self->{connections}{$id} = {cb => $cb, nb => $nb, tx => $tx};
return $id;
@@ -424,19 +427,20 @@ Mojo::UserAgent - Non-blocking I/O HTTP and WebSocket user
=head1 DESCRIPTION
L<Mojo::UserAgent> is a full featured non-blocking I/O HTTP and WebSocket user
-agent, with IPv6, TLS, SNI, IDNA, Comet (long polling), keep-alive, connection
-pooling, timeout, cookie, multipart, proxy, gzip compression and multiple
-event loop support.
+agent, with IPv6, SOCKS, TLS, SNI, IDNA, Comet (long polling), keep-alive,
+connection pooling, timeout, cookie, multipart, proxy, gzip compression and
+multiple event loop support.
All connections will be reset automatically if a new process has been forked,
this allows multiple processes to share the same L<Mojo::UserAgent> object
safely.
-For better scalability (epoll, kqueue) and to provide IPv6 as well as TLS
-support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+) and
-L<IO::Socket::SSL> (1.84+) will be used automatically by L<Mojo::IOLoop> if
-they are installed. Individual features can also be disabled with the
-C<MOJO_NO_IPV6> and C<MOJO_NO_TLS> environment variables.
+For better scalability (epoll, kqueue) and to provide IPv6, SOCKS as well as
+TLS support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+),
+L<IO::Socket::Socks> (0.63+) and L<IO::Socket::SSL> (1.84+) will be used
+automatically if they are installed. Individual features can also be disabled
+with the C<MOJO_NO_IPV6>, C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment
+variables.
See L<Mojolicious::Guides::Cookbook/"USER AGENT"> for more.
@@ -580,6 +584,9 @@ Proxy manager, defaults to a L<Mojo::UserAgent::Proxy> objec
# Detect proxy servers from environment
$ua->proxy->detect;
+ # Manually configure Tor
+ $ua->proxy->http('socks://127.0.0.1:9050')->https('socks://127.0.0.1:9050');
+
=head2 request_timeout
my $timeout = $ua->request_timeout;
diff --git a/lib/Mojo/UserAgent/Transactor.pm b/lib/Mojo/UserAgent/Transactor.pm
index 15740c4..2a90369 100644
--- a/lib/Mojo/UserAgent/Transactor.pm
+++ b/lib/Mojo/UserAgent/Transactor.pm
@@ -50,6 +50,7 @@ sub proxy_connect {
# No proxy
return undef unless my $proxy = $req->proxy;
+ return undef if $proxy->protocol eq 'socks';
# WebSocket and/or HTTPS
my $url = $req->url;
diff --git a/lib/Mojolicious/Command/version.pm b/lib/Mojolicious/Command/versio
index 56c1612..ec54e97 100644
--- a/lib/Mojolicious/Command/version.pm
+++ b/lib/Mojolicious/Command/version.pm
@@ -1,7 +1,7 @@
package Mojolicious::Command::version;
use Mojo::Base 'Mojolicious::Command';
-use Mojo::IOLoop::Server;
+use Mojo::IOLoop::Client;
use Mojo::UserAgent;
use Mojolicious;
@@ -11,11 +11,11 @@ has usage => sub { shift->extract_usage };
sub run {
my $self = shift;
- my $ev = eval 'use Mojo::Reactor::EV; 1' ? $EV::VERSION : 'not installed';
- my $ipv6
- = Mojo::IOLoop::Server::IPV6 ? $IO::Socket::IP::VERSION : 'not installed';
- my $tls
- = Mojo::IOLoop::Server::TLS ? $IO::Socket::SSL::VERSION : 'not installed';
+ my $ev = eval 'use Mojo::Reactor::EV; 1' ? $EV::VERSION : 'not installed';
+ my $class = 'Mojo::IOLoop::Client';
+ my $ipv6 = $class->IPV6 ? $IO::Socket::IP::VERSION : 'not installed';
+ my $socks = $class->SOCKS ? $IO::Socket::Socks::VERSION : 'not installed';
+ my $tls = $class->TLS ? $IO::Socket::SSL::VERSION : 'not installed';
print <<EOF;
CORE
@@ -23,9 +23,10 @@ CORE
Mojolicious ($Mojolicious::VERSION, $Mojolicious::CODENAME)
OPTIONAL
- EV 4.0+ ($ev)
- IO::Socket::IP 0.20+ ($ipv6)
- IO::Socket::SSL 1.84+ ($tls)
+ EV 4.0+ ($ev)
+ IO::Socket::IP 0.20+ ($ipv6)
+ IO::Socket::Socks 0.63+ ($ipv6)
+ IO::Socket::SSL 1.84+ ($tls)
EOF
diff --git a/t/mojo/transactor.t b/t/mojo/transactor.t
index 9710c45..197d516 100644
--- a/t/mojo/transactor.t
+++ b/t/mojo/transactor.t
@@ -494,6 +494,9 @@ is $tx->req->headers->host, 'mojolicio.us', 'right "Host" he
is $t->proxy_connect($tx), undef, 'already a CONNECT request';
$tx->req->method('Connect');
is $t->proxy_connect($tx), undef, 'already a CONNECT request';
+$tx = $t->tx(GET => 'http://mojolicio.us');
+$tx->req->proxy('socks://127.0.0.1:3000');
+is $t->proxy_connect($tx), undef, 'using a SOCKS proxy';
# Simple 301 redirect
$tx = $t->tx(
diff --git a/t/mojo/user_agent.t b/t/mojo/user_agent.t
index a0dce4c..1b44c97 100644
--- a/t/mojo/user_agent.t
+++ b/t/mojo/user_agent.t
@@ -1,7 +1,7 @@
use Mojo::Base -strict;
BEGIN {
- $ENV{MOJO_NO_IPV6} = $ENV{MOJO_NO_TLS} = 1;
+ $ENV{MOJO_NO_IPV6} = $ENV{MOJO_NO_SOCKS} = $ENV{MOJO_NO_TLS} = 1;
$ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll';
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment