about summary refs log tree commit homepage
path: root/t
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2016-03-03 10:33:02 +0000
committerEric Wong <e@80x24.org>2016-03-04 00:25:43 +0000
commit8557833d769280495ababfa71f202bf131ea5512 (patch)
tree9f988d17af1e75a95badfe6d1eb083ee9ad7fdd9 /t
parentb38de6f02fa04e36b881d2aad9c7f792beb0b6a1 (diff)
downloadpublic-inbox-8557833d769280495ababfa71f202bf131ea5512.tar.gz
Listening on Unix domain sockets can be convenient for running
behind reverse proxies, avoiding port conflicts, limiting access,
or avoiding the overhead (if any) of TCP over loopback.
Diffstat (limited to 't')
-rw-r--r--t/httpd-corner.t27
-rw-r--r--t/httpd-unix.t105
2 files changed, 130 insertions, 2 deletions
diff --git a/t/httpd-corner.t b/t/httpd-corner.t
index 198a7e90..19564074 100644
--- a/t/httpd-corner.t
+++ b/t/httpd-corner.t
@@ -16,6 +16,7 @@ use Digest::SHA qw(sha1_hex);
 use File::Temp qw/tempdir/;
 use Cwd qw/getcwd/;
 use IO::Socket;
+use IO::Socket::UNIX;
 use Fcntl qw(FD_CLOEXEC F_SETFD F_GETFD :seek);
 use Socket qw(SO_KEEPALIVE IPPROTO_TCP TCP_NODELAY);
 use POSIX qw(dup2 mkfifo :sys_wait_h);
@@ -34,20 +35,32 @@ my %opts = (
         Listen => 1024,
 );
 my $sock = IO::Socket::INET->new(%opts);
+my $upath = "$tmpdir/s";
+my $unix = IO::Socket::UNIX->new(
+        Listen => 1024,
+        Type => SOCK_STREAM,
+        Local => $upath
+);
+ok($unix, 'UNIX socket created');
 my $pid;
 END { kill 'TERM', $pid if defined $pid };
 my $spawn_httpd = sub {
         my (@args) = @_;
+        $! = 0;
         my $fl = fcntl($sock, F_GETFD, 0);
         ok(! $!, 'no error from fcntl(F_GETFD)');
         is($fl, FD_CLOEXEC, 'cloexec set by default (Perl behavior)');
         $pid = fork;
         if ($pid == 0) {
                 # pretend to be systemd
-                fcntl($sock, F_SETFD, $fl &= ~FD_CLOEXEC);
                 dup2(fileno($sock), 3) or die "dup2 failed: $!\n";
+                dup2(fileno($unix), 4) or die "dup2 failed: $!\n";
+                $sock = IO::Handle->new_from_fd(3, 'r');
+                $sock->fcntl(F_SETFD, 0);
+                $unix = IO::Handle->new_from_fd(4, 'r');
+                $unix->fcntl(F_SETFD, 0);
                 $ENV{LISTEN_PID} = $$;
-                $ENV{LISTEN_FDS} = 1;
+                $ENV{LISTEN_FDS} = 2;
                 exec $httpd, @args, "--stdout=$out", "--stderr=$err", $psgi;
                 die "FAIL: $!\n";
         }
@@ -63,6 +76,16 @@ my $spawn_httpd = sub {
         $spawn_httpd->('-W0');
 }
 
+# Unix domain sockets
+{
+        my $u = IO::Socket::UNIX->new(Type => SOCK_STREAM, Peer => $upath);
+        ok($u, 'unix socket connected');
+        $u->write("GET /host-port HTTP/1.0\r\n\r\n");
+        $u->read(my $buf, 4096);
+        like($buf, qr!\r\n\r\n127\.0\.0\.1:0\z!,
+                'set REMOTE_ADDR and REMOTE_PORT for Unix socket');
+}
+
 sub conn_for {
         my ($sock, $msg) = @_;
         my $conn = IO::Socket::INET->new(
diff --git a/t/httpd-unix.t b/t/httpd-unix.t
new file mode 100644
index 00000000..580d14d2
--- /dev/null
+++ b/t/httpd-unix.t
@@ -0,0 +1,105 @@
+# Copyright (C) 2016 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+# Tests for binding Unix domain sockets
+use strict;
+use warnings;
+use Test::More;
+
+foreach my $mod (qw(Plack::Util Plack::Request Plack::Builder Danga::Socket
+                        HTTP::Parser::XS HTTP::Date HTTP::Status)) {
+        eval "require $mod";
+        plan skip_all => "$mod missing for httpd-unix.t" if $@;
+}
+
+use File::Temp qw/tempdir/;
+use IO::Socket::UNIX;
+use Cwd qw/getcwd/;
+use Fcntl qw(FD_CLOEXEC F_SETFD F_GETFD :seek);
+my $tmpdir = tempdir('httpd-unix-XXXXXX', TMPDIR => 1, CLEANUP => 1);
+my $unix = "$tmpdir/unix.sock";
+my $httpd = 'blib/script/public-inbox-httpd';
+my $psgi = getcwd() . '/t/httpd-corner.psgi';
+my $out = "$tmpdir/out.log";
+my $err = "$tmpdir/err.log";
+
+my $pid;
+END { kill 'TERM', $pid if defined $pid };
+
+my $spawn_httpd = sub {
+        my (@args) = @_;
+        $pid = fork;
+        if ($pid == 0) {
+                exec $httpd, @args, "--stdout=$out", "--stderr=$err", $psgi;
+                die "FAIL: $!\n";
+        }
+        ok(defined $pid, 'forked httpd process successfully');
+};
+
+ok(!-S $unix, 'UNIX socket does not exist, yet');
+$spawn_httpd->("-l$unix");
+for (1..1000) {
+        last if -S $unix;
+        select undef, undef, undef, 0.02
+}
+
+ok(-S $unix, 'UNIX socket was bound by -httpd');
+sub check_sock ($) {
+        my ($unix) = @_;
+        my $sock = IO::Socket::UNIX->new(Peer => $unix, Type => SOCK_STREAM);
+        ok($sock, 'client UNIX socket connected');
+        ok($sock->write("GET /host-port HTTP/1.0\r\n\r\n"),
+                'wrote req to server');
+        ok($sock->read(my $buf, 4096), 'read response');
+        like($buf, qr!\r\n\r\n127\.0\.0\.1:0\z!,
+                'set REMOTE_ADDR and REMOTE_PORT for Unix socket');
+}
+
+check_sock($unix);
+
+{ # do not clobber existing socket
+        my $fpid = fork;
+        if ($fpid == 0) {
+                open STDOUT, '>>', "$tmpdir/1" or die "redirect failed: $!";
+                open STDERR, '>>', "$tmpdir/2" or die "redirect failed: $!";
+                exec $httpd, '-l', $unix, '-W0', $psgi;
+                die "FAIL: $!\n";
+        }
+        is($fpid, waitpid($fpid, 0), 'second httpd exits');
+        isnt($?, 0, 'httpd failed with failure to bind');
+        open my $fh, "$tmpdir/2" or die "failed to open $tmpdir/2: $!";
+        local $/;
+        my $e = <$fh>;
+        like($e, qr/no listeners bound/i, 'got error message');
+        is(-s "$tmpdir/1", 0, 'stdout was empty');
+}
+
+{
+        my $kpid = $pid;
+        $pid = undef;
+        is(kill('TERM', $kpid), 1, 'terminate existing process');
+        is(waitpid($kpid, 0), $kpid, 'existing httpd terminated');
+        is($?, 0, 'existing httpd exited successfully');
+        ok(-S $unix, 'unix socket still exists');
+}
+{
+        # wait for daemonization
+        $spawn_httpd->("-l$unix", '-D', '-P', "$tmpdir/pid");
+        my $kpid = $pid;
+        $pid = undef;
+        is(waitpid($kpid, 0), $kpid, 'existing httpd terminated');
+        check_sock($unix);
+
+        ok(-f "$tmpdir/pid", 'pid file written');
+        open my $fh, '<', "$tmpdir/pid" or die "open failed: $!";
+        my $rpid = <$fh>;
+        chomp $rpid;
+        like($rpid, qr/\A\d+\z/s, 'pid file looks like a pid');
+        is(kill('TERM', $rpid), 1, 'signalled daemonized process');
+        for (1..100) {
+                kill(0, $rpid) or last;
+                select undef, undef, undef, 0.02;
+        }
+        is(kill(0, $rpid), 0, 'daemonized process exited')
+}
+
+done_testing();