From 925763280eb386b6f5c4b6e065db505952778802 Mon Sep 17 00:00:00 2001 From: andig Date: Sat, 7 Oct 2017 18:20:27 +0200 Subject: [PATCH] Add Unix domain socket (UDS) support to Server with unix:// URI scheme --- README.md | 8 ++++++++ examples/01-echo.php | 5 +++++ examples/02-chat-server.php | 5 +++++ examples/03-benchmark.php | 10 +++++++-- src/Server.php | 13 ++++++++---- tests/ServerTest.php | 41 +++++++++++++++++++++++++++++++++++++ 6 files changed, 76 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 351d1e44..344f0f0c 100644 --- a/README.md +++ b/README.md @@ -344,6 +344,7 @@ Calling this method more than once on the same instance is a NO-OP. The `Server` class is the main class in this package that implements the [`ServerInterface`](#serverinterface) and allows you to accept incoming streaming connections, such as plaintext TCP/IP or secure TLS connection streams. +Connections can also be accepted on Unix domain sockets. ```php $server = new Server(8080, $loop); @@ -375,6 +376,13 @@ brackets: $server = new Server('[::1]:8080', $loop); ``` +To listen on a Unix domain socket (UDS) path, you MUST prefix the URI with the +`unix://` scheme: + +```php +$server = new Server('unix:///tmp/server.sock', $loop); +``` + If the given URI is invalid, does not contain a port, any other scheme or if it contains a hostname, it will throw an `InvalidArgumentException`: diff --git a/examples/01-echo.php b/examples/01-echo.php index 18bf9d3b..e6dfffda 100644 --- a/examples/01-echo.php +++ b/examples/01-echo.php @@ -10,6 +10,11 @@ // // $ php examples/01-echo.php tls://127.0.0.1:8000 examples/localhost.pem // $ openssl s_client -connect localhost:8000 +// +// You can also run a Unix domain socket (UDS) server like this: +// +// $ php examples/01-echo.php unix:///tmp/server.sock +// $ nc -U /tmp/server.sock use React\EventLoop\Factory; use React\Socket\Server; diff --git a/examples/02-chat-server.php b/examples/02-chat-server.php index 43d0c576..46439e04 100644 --- a/examples/02-chat-server.php +++ b/examples/02-chat-server.php @@ -10,6 +10,11 @@ // // $ php examples/02-chat-server.php tls://127.0.0.1:8000 examples/localhost.pem // $ openssl s_client -connect localhost:8000 +// +// You can also run a Unix domain socket (UDS) server like this: +// +// $ php examples/02-chat-server.php unix:///tmp/server.sock +// $ nc -U /tmp/server.sock use React\EventLoop\Factory; use React\Socket\Server; diff --git a/examples/03-benchmark.php b/examples/03-benchmark.php index 8f71707a..e8fbfac5 100644 --- a/examples/03-benchmark.php +++ b/examples/03-benchmark.php @@ -6,8 +6,8 @@ // // $ php examples/03-benchmark.php 8000 // $ telnet localhost 8000 -// $ echo hello world | nc -v localhost 8000 -// $ dd if=/dev/zero bs=1M count=1000 | nc -v localhost 8000 +// $ echo hello world | nc -N localhost 8000 +// $ dd if=/dev/zero bs=1M count=1000 | nc -N localhost 8000 // // You can also run a secure TLS benchmarking server like this: // @@ -15,6 +15,12 @@ // $ openssl s_client -connect localhost:8000 // $ echo hello world | openssl s_client -connect localhost:8000 // $ dd if=/dev/zero bs=1M count=1000 | openssl s_client -connect localhost:8000 +// +// You can also run a Unix domain socket (UDS) server benchmark like this: +// +// $ php examples/03-benchmark.php unix:///tmp/server.sock +// $ nc -N -U /tmp/server.sock +// $ dd if=/dev/zero bs=1M count=1000 | nc -N -U /tmp/server.sock use React\EventLoop\Factory; use React\Socket\Server; diff --git a/src/Server.php b/src/Server.php index 86601f78..8c46e1ef 100644 --- a/src/Server.php +++ b/src/Server.php @@ -12,7 +12,7 @@ final class Server extends EventEmitter implements ServerInterface public function __construct($uri, LoopInterface $loop, array $context = array()) { // sanitize TCP context options if not properly wrapped - if ($context && (!isset($context['tcp']) && !isset($context['tls']))) { + if ($context && (!isset($context['tcp']) && !isset($context['tls']) && !isset($context['unix']))) { $context = array('tcp' => $context); } @@ -20,6 +20,7 @@ public function __construct($uri, LoopInterface $loop, array $context = array()) $context += array( 'tcp' => array(), 'tls' => array(), + 'unix' => array() ); $scheme = 'tcp'; @@ -28,10 +29,14 @@ public function __construct($uri, LoopInterface $loop, array $context = array()) $scheme = substr($uri, 0, $pos); } - $server = new TcpServer(str_replace('tls://', '', $uri), $loop, $context['tcp']); + if ($scheme === 'unix') { + $server = new UnixServer($uri, $loop, $context['unix']); + } else { + $server = new TcpServer(str_replace('tls://', '', $uri), $loop, $context['tcp']); - if ($scheme === 'tls') { - $server = new SecureServer($server, $loop, $context['tls']); + if ($scheme === 'tls') { + $server = new SecureServer($server, $loop, $context['tls']); + } } $this->server = $server; diff --git a/tests/ServerTest.php b/tests/ServerTest.php index 5c24e6ca..1a5d2a90 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -4,11 +4,15 @@ use React\EventLoop\Factory; use React\Socket\Server; +use React\Socket\TcpConnector; +use React\Socket\UnixConnector; use Clue\React\Block; use React\Socket\ConnectionInterface; class ServerTest extends TestCase { + const TIMEOUT = 0.1; + public function testCreateServer() { $loop = Factory::create(); @@ -26,6 +30,38 @@ public function testConstructorThrowsForInvalidUri() $server = new Server('invalid URI', $loop); } + public function testConstructorCreatesExpectedTcpServer() + { + $loop = Factory::create(); + + $server = new Server(0, $loop); + + $connector = new TcpConnector($loop); + $connector->connect($server->getAddress()) + ->then($this->expectCallableOnce(), $this->expectCallableNever()); + + $connection = Block\await($connector->connect($server->getAddress()), $loop, self::TIMEOUT); + + $connection->close(); + $server->close(); + } + + public function testConstructorCreatesExpectedUnixServer() + { + $loop = Factory::create(); + + $server = new Server($this->getRandomSocketUri(), $loop); + + $connector = new UnixConnector($loop); + $connector->connect($server->getAddress()) + ->then($this->expectCallableOnce(), $this->expectCallableNever()); + + $connection = Block\await($connector->connect($server->getAddress()), $loop, self::TIMEOUT); + + $connection->close(); + $server->close(); + } + public function testEmitsConnectionForNewConnection() { $loop = Factory::create(); @@ -127,4 +163,9 @@ public function testDoesNotEmitSecureConnectionForNewPlainConnection() Block\sleep(0.1, $loop); } + + private function getRandomSocketUri() + { + return "unix://" . sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(rand(), true) . '.sock'; + } }