Skip to content

Commit

Permalink
add support for UNIX domain sockets and fix a few bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
kraih committed Feb 22, 2017
1 parent 95b3769 commit 8f6e214
Show file tree
Hide file tree
Showing 16 changed files with 214 additions and 75 deletions.
2 changes: 2 additions & 0 deletions Changes
@@ -1,5 +1,7 @@

7.27 2017-02-22
- Added support for UNIX domain sockets. (sri, salva)
- Fixed a bug in Mojo::URL where invalid host strings could be generated.
- Fixed blib handling in Mojo::Home.

7.26 2017-02-15
Expand Down
5 changes: 3 additions & 2 deletions README.md
Expand Up @@ -21,8 +21,9 @@
* A powerful **web development toolkit**, that you can use for all kinds of
applications, independently of the web framework.
* Full stack HTTP and WebSocket client/server implementation with IPv6, TLS,
SNI, IDNA, HTTP/SOCKS5 proxy, Comet (long polling), keep-alive, connection
pooling, timeout, cookie, multipart, and gzip compression support.
SNI, IDNA, HTTP/SOCKS5 proxy, UNIX domain socket, Comet (long polling),
keep-alive, connection pooling, timeout, cookie, multipart, and gzip
compression support.
* Built-in non-blocking I/O web server, supporting multiple event loops as
well as optional pre-forking and hot deployment, perfect for building
highly scalable web services.
Expand Down
10 changes: 5 additions & 5 deletions lib/Mojo/IOLoop.pm
Expand Up @@ -281,7 +281,7 @@ Mojo::IOLoop - Minimalistic event loop
L<Mojo::IOLoop> is a very minimalistic event loop based on L<Mojo::Reactor>, it
has been reduced to the absolute minimal feature set required to build solid
and scalable non-blocking TCP clients and servers.
and scalable non-blocking clients and servers.
Depending on operating system, the default per-process and system-wide file
descriptor limits are often very low and need to be tuned for better
Expand Down Expand Up @@ -390,8 +390,8 @@ Get L<Mojo::IOLoop::Server> object for id or turn object into an acceptor.
my $id = $loop->client(address => '127.0.0.1', port => 3000, sub {...});
my $id = $loop->client({address => '127.0.0.1', port => 3000} => sub {...});
Open TCP connection with L<Mojo::IOLoop::Client>, takes the same arguments as
L<Mojo::IOLoop::Client/"connect">.
Open TCP/IP or UNIX domain socket connection with L<Mojo::IOLoop::Client>, takes
the same arguments as L<Mojo::IOLoop::Client/"connect">.
# Connect to 127.0.0.1 on port 3000
Mojo::IOLoop->client({port => 3000} => sub {
Expand Down Expand Up @@ -530,8 +530,8 @@ Remove everything and stop the event loop.
my $id = $loop->server(port => 3000, sub {...});
my $id = $loop->server({port => 3000} => sub {...});
Accept TCP connections with L<Mojo::IOLoop::Server>, takes the same arguments
as L<Mojo::IOLoop::Server/"listen">.
Accept TCP/IP and UNIX domain socket connections with L<Mojo::IOLoop::Server>,
takes the same arguments as L<Mojo::IOLoop::Server/"listen">.
# Listen on port 3000
Mojo::IOLoop->server({port => 3000} => sub {
Expand Down
45 changes: 33 additions & 12 deletions lib/Mojo/IOLoop/Client.pm
Expand Up @@ -3,6 +3,7 @@ use Mojo::Base 'Mojo::EventEmitter';

use Errno 'EINPROGRESS';
use IO::Socket::IP;
use IO::Socket::UNIX;
use Mojo::IOLoop;
use Mojo::IOLoop::TLS;
use Scalar::Util 'weaken';
Expand Down Expand Up @@ -41,7 +42,7 @@ sub connect {
$_ && s/[[\]]//g for @$args{qw(address socks_address)};
my $address = $args->{socks_address} || ($args->{address} ||= '127.0.0.1');
return $reactor->next_tick(sub { $self && $self->_connect($args) })
if !NNR || $args->{handle};
if !NNR || $args->{handle} || $args->{path};

# Non-blocking name resolution
my $handle = $self->{dns} = $NDN->getaddrinfo($address, _port($args),
Expand Down Expand Up @@ -71,19 +72,32 @@ sub _cleanup {
sub _connect {
my ($self, $args) = @_;

my $handle;
my $address = $args->{socks_address} || $args->{address};
unless ($handle = $self->{handle} = $args->{handle}) {
my %options = (PeerAddr => $address, PeerPort => _port($args));
%options = (PeerAddrInfo => $args->{addr_info}) if $args->{addr_info};
$options{Blocking} = 0;
$options{LocalAddr} = $args->{local_address} if $args->{local_address};
my $path = $args->{path};
my $handle = $self->{handle} = $args->{handle};

unless ($handle) {
my $class = $path ? 'IO::Socket::UNIX' : 'IO::Socket::IP';
my %options = (Blocking => 0);

# UNIX domain socket
if ($path) { $options{Peer} = $path }

# IP socket
else {
if (my $info = $args->{addr_info}) { $options{PeerAddrInfo} = $info }
else {
$options{PeerAddr} = $args->{socks_address} || $args->{address};
$options{PeerPort} = _port($args);
}
$options{LocalAddr} = $args->{local_address} if $args->{local_address};
}

return $self->emit(error => "Can't connect: $@")
unless $self->{handle} = $handle = IO::Socket::IP->new(%options);
unless $self->{handle} = $handle = $class->new(%options);
}
$handle->blocking(0);

$self->_wait('_ready', $handle, $args);
$path ? $self->_try_socks($args) : $self->_wait('_ready', $handle, $args);
}

sub _port { $_[0]{socks_port} || $_[0]{port} || ($_[0]{tls} ? 443 : 80) }
Expand Down Expand Up @@ -170,7 +184,7 @@ sub _wait {
=head1 NAME
Mojo::IOLoop::Client - Non-blocking TCP client
Mojo::IOLoop::Client - Non-blocking TCP/IP and UNIX domain socket client
=head1 SYNOPSIS
Expand All @@ -193,7 +207,8 @@ Mojo::IOLoop::Client - Non-blocking TCP client
=head1 DESCRIPTION
L<Mojo::IOLoop::Client> opens TCP connections for L<Mojo::IOLoop>.
L<Mojo::IOLoop::Client> opens TCP/IP and UNIX domain socket connections for
L<Mojo::IOLoop>.
=head1 EVENTS
Expand Down Expand Up @@ -279,6 +294,12 @@ Use an already prepared L<IO::Socket::IP> object.
Local address to bind to.
=item path
path => '/tmp/myapp.sock'
Path of UNIX domain socket to connect to.
=item port
port => 80
Expand Down
61 changes: 42 additions & 19 deletions lib/Mojo/IOLoop/Server.pm
Expand Up @@ -3,6 +3,7 @@ use Mojo::Base 'Mojo::EventEmitter';

use Carp 'croak';
use IO::Socket::IP;
use IO::Socket::UNIX;
use Mojo::IOLoop;
use Mojo::IOLoop::TLS;
use Scalar::Util 'weaken';
Expand All @@ -28,37 +29,52 @@ sub listen {
my ($self, $args) = (shift, ref $_[0] ? $_[0] : {@_});

# Look for reusable file descriptor
my $path = $self->{path} = $args->{path};
my $address = $args->{address} || '0.0.0.0';
my $port = $args->{port};
$ENV{MOJO_REUSE} ||= '';
my $fd;
$fd = $1 if $port && $ENV{MOJO_REUSE} =~ /(?:^|\,)\Q$address:$port\E:(\d+)/;
my $fd
= ($path && $ENV{MOJO_REUSE} =~ /(?:^|\,)unix:\Q$path\E:(\d+)/)
|| ($port && $ENV{MOJO_REUSE} =~ /(?:^|\,)\Q$address:$port\E:(\d+)/)
? $1
: undef;

# Allow file descriptor inheritance
local $^F = 1023;

# Reuse file descriptor
my $handle;
my $class = $path ? 'IO::Socket::UNIX' : 'IO::Socket::IP';
if (defined $fd) {
$handle = IO::Socket::IP->new_from_fd($fd, 'r')
$handle = $class->new_from_fd($fd, 'r')
or croak "Can't open file descriptor $fd: $!";
}

# New socket
else {
my %options = (
Listen => $args->{backlog} // SOMAXCONN,
LocalAddr => $address,
ReuseAddr => 1,
ReusePort => $args->{reuse},
Type => SOCK_STREAM
);
$options{LocalPort} = $port if $port;
$options{LocalAddr} =~ s/[\[\]]//g;
$handle = IO::Socket::IP->new(%options)
or croak "Can't create listen socket: $@";
$fd = fileno $handle;
my $reuse = $self->{reuse} = join ':', $address, $handle->sockport, $fd;
my %options
= (Listen => $args->{backlog} // SOMAXCONN, Type => SOCK_STREAM);

# UNIX domain socket
my $reuse;
if ($path) {
unlink $path if -S $self->{path};
$options{Local} = $path;
$handle = $class->new(%options) or croak "Can't create listen socket: $!";
$reuse = $self->{reuse} = join ':', 'unix', $path, fileno $handle;
}

# IP socket
else {
$options{LocalAddr} = $address;
$options{LocalAddr} =~ s/[\[\]]//g;
$options{LocalPort} = $port if $port;
$options{ReuseAddr} = 1;
$options{ReusePort} = $args->{reuse};
$handle = $class->new(%options) or croak "Can't create listen socket: $@";
$fd = fileno $handle;
$reuse = $self->{reuse} = join ':', $address, $handle->sockport, $fd;
}

$ENV{MOJO_REUSE} .= length $ENV{MOJO_REUSE} ? ",$reuse" : "$reuse";
}
$handle->blocking(0);
Expand Down Expand Up @@ -108,7 +124,7 @@ sub _accept {
=head1 NAME
Mojo::IOLoop::Server - Non-blocking TCP server
Mojo::IOLoop::Server - Non-blocking TCP and UNIX domain socket server
=head1 SYNOPSIS
Expand All @@ -131,7 +147,8 @@ Mojo::IOLoop::Server - Non-blocking TCP server
=head1 DESCRIPTION
L<Mojo::IOLoop::Server> accepts TCP connections for L<Mojo::IOLoop>.
L<Mojo::IOLoop::Server> accepts TCP/IP and UNIX domain socket connections for
L<Mojo::IOLoop>.
=head1 EVENTS
Expand Down Expand Up @@ -206,6 +223,12 @@ Local address to listen on, defaults to C<0.0.0.0>.
Maximum backlog size, defaults to C<SOMAXCONN>.
=item path
path => '/tmp/myapp.sock'
Path for UNIX domain socket to listen on.
=item port
port => 80
Expand Down
22 changes: 15 additions & 7 deletions lib/Mojo/Server/Daemon.pm
Expand Up @@ -85,8 +85,10 @@ sub _build_tx {
my $tx = $self->build_tx->connection($id);
$tx->res->headers->server('Mojolicious (Perl)');
my $handle = $self->ioloop->stream($id)->handle;
$tx->local_address($handle->sockhost)->local_port($handle->sockport);
$tx->remote_address($handle->peerhost)->remote_port($handle->peerport);
unless ($handle->isa('IO::Socket::UNIX')) {
$tx->local_address($handle->sockhost)->local_port($handle->sockport);
$tx->remote_address($handle->peerhost)->remote_port($handle->peerport);
}
$tx->req->url->base->scheme('https') if $c->{tls};

weaken $self;
Expand Down Expand Up @@ -166,24 +168,27 @@ sub _listen {

my $url = Mojo::URL->new($listen);
my $proto = $url->protocol;
croak qq{Invalid listen location "$listen"} unless $proto =~ /^https?$/;
croak qq{Invalid listen location "$listen"}
unless $proto eq 'http' || $proto eq 'https' || $proto eq 'http+unix';

my $query = $url->query;
my $options = {
address => $url->host,
backlog => $self->backlog,
single_accept => $query->param('single_accept'),
reuse => $query->param('reuse')
};
if (my $port = $url->port) { $options->{port} = $port }
if ($proto eq 'http+unix') { $options->{path} = $url->host }
else {
if ((my $host = $url->host) ne '*') { $options->{address} = $host }
if (my $port = $url->port) { $options->{port} = $port }
}
$options->{"tls_$_"} = $query->param($_) for qw(ca ciphers version);
/^(.*)_(cert|key)$/ and $options->{"tls_$2"}{$1} = $query->param($_)
for @{$query->names};
if (my $cert = $query->param('cert')) { $options->{'tls_cert'}{''} = $cert }
if (my $key = $query->param('key')) { $options->{'tls_key'}{''} = $key }
my $verify = $query->param('verify');
$options->{tls_verify} = hex $verify if defined $verify;
delete $options->{address} if $options->{address} eq '*';
my $tls = $options->{tls} = $proto eq 'https';

weaken $self;
Expand All @@ -207,7 +212,7 @@ sub _listen {
$self->app->log->info(qq{Listening at "$url"});
$query->pairs([]);
$url->host('127.0.0.1') if $url->host eq '*';
say "Server available at $url";
say 'Server available at ', $options->{path} // $url;
}

sub _read {
Expand Down Expand Up @@ -362,6 +367,9 @@ C<http://0.0.0.0:3000>).
# Listen on IPv4 and IPv6 interfaces
$daemon->listen(['http://127.0.0.1:3000', 'http://[::1]:3000']);
# Listen on UNIX domain socket "/tmp/myapp.sock" (percent encoded slash)
$daemon->listen(['http+unix://%2Ftmp%2Fmyapp.sock']);
# Allow multiple servers to use the same port (SO_REUSEPORT)
$daemon->listen(['http://*:8080?reuse=1']);
Expand Down
8 changes: 4 additions & 4 deletions lib/Mojo/Server/Hypnotoad.pm
Expand Up @@ -161,10 +161,10 @@ Mojo::Server::Hypnotoad - A production web serv...ALL GLORY TO THE HYPNOTOAD!
L<Mojo::Server::Hypnotoad> is a full featured, UNIX optimized, pre-forking
non-blocking I/O HTTP and WebSocket server, built around the very well tested
and reliable L<Mojo::Server::Prefork>, with IPv6, TLS, SNI, Comet (long
polling), keep-alive, multiple event loop and hot deployment support that just
works. Note that the server uses signals for process management, so you should
avoid modifying signal handlers in your applications.
and reliable L<Mojo::Server::Prefork>, with IPv6, TLS, SNI, UNIX domain socket,
Comet (long polling), keep-alive, multiple event loop and hot deployment support
that just works. Note that the server uses signals for process management, so
you should avoid modifying signal handlers in your applications.
To start applications with it you can use the L<hypnotoad> script, which
listens on port C<8080>, automatically daemonizes the server process and
Expand Down
7 changes: 4 additions & 3 deletions lib/Mojo/Server/Morbo.pm
Expand Up @@ -103,9 +103,10 @@ Mojo::Server::Morbo - Tonight at 11...DOOOOOOOOOOOOOOOM!
L<Mojo::Server::Morbo> is a full featured, self-restart capable non-blocking
I/O HTTP and WebSocket server, built around the very well tested and reliable
L<Mojo::Server::Daemon>, with IPv6, TLS, SNI, Comet (long polling), keep-alive
and multiple event loop support. Note that the server uses signals for process
management, so you should avoid modifying signal handlers in your applications.
L<Mojo::Server::Daemon>, with IPv6, TLS, SNI, UNIX domain socket, Comet (long
polling), keep-alive and multiple event loop support. Note that the server uses
signals for process management, so you should avoid modifying signal handlers in
your applications.
To start applications with it you can use the L<morbo> script.
Expand Down
8 changes: 4 additions & 4 deletions lib/Mojo/Server/Prefork.pm
Expand Up @@ -225,10 +225,10 @@ Mojo::Server::Prefork - Pre-forking non-blocking I/O HTTP and WebSocket server
L<Mojo::Server::Prefork> is a full featured, UNIX optimized, pre-forking
non-blocking I/O HTTP and WebSocket server, built around the very well tested
and reliable L<Mojo::Server::Daemon>, with IPv6, TLS, SNI, Comet (long polling),
keep-alive and multiple event loop support. Note that the server uses signals
for process management, so you should avoid modifying signal handlers in your
applications.
and reliable L<Mojo::Server::Daemon>, with IPv6, TLS, SNI, UNIX domain socket,
Comet (long polling), keep-alive and multiple event 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 non-blocking name
resolution, SOCKS5 as well as TLS support, the optional modules L<EV> (4.0+),
Expand Down
5 changes: 3 additions & 2 deletions lib/Mojo/URL.pm
Expand Up @@ -42,10 +42,10 @@ sub ihost {

# Check if host needs to be encoded
return undef unless defined(my $host = $self->host);
return lc $host unless $host =~ /[^\x00-\x7f]/;
return $host unless $host =~ /[^\x00-\x7f]/;

# Encode
return lc join '.',
return join '.',
map { /[^\x00-\x7f]/ ? ('xn--' . punycode_encode $_) : $_ }
split(/\./, $host, -1);
}
Expand Down Expand Up @@ -166,6 +166,7 @@ sub _string {

# Authority
my $auth = $self->host_port;
$auth = _encode($auth, '^A-Za-z0-9\-._~!$&\'()*+,;=:\[\]') if defined $auth;
if ($unsafe && defined(my $info = $self->userinfo)) {
$auth = _encode($info, '^A-Za-z0-9\-._~!$&\'()*+,;=:') . '@' . $auth;
}
Expand Down

0 comments on commit 8f6e214

Please sign in to comment.