Skip to content

Commit 7f4814f

Browse files
andigclue
authored andcommitted
Add advanced UnixServer
1 parent d169d1e commit 7f4814f

3 files changed

Lines changed: 448 additions & 1 deletion

File tree

README.md

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ handle multiple concurrent connections without blocking.
3434
* [Advanced server usage](#advanced-server-usage)
3535
* [TcpServer](#tcpserver)
3636
* [SecureServer](#secureserver)
37+
* [UnixServer](#unixserver)
3738
* [LimitingServer](#limitingserver)
3839
* [getConnections()](#getconnections)
3940
* [Client usage](#client-usage)
@@ -255,7 +256,8 @@ If the address can not be determined or is unknown at this time (such as
255256
after the socket has been closed), it MAY return a `NULL` value instead.
256257

257258
Otherwise, it will return the full address (URI) as a string value, such
258-
as `tcp://127.0.0.1:8080`, `tcp://[::1]:80` or `tls://127.0.0.1:443`.
259+
as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`
260+
`unix://example.sock` or `unix:///path/to/example.sock`.
259261
Note that individual URI components are application specific and depend
260262
on the underlying transport protocol.
261263

@@ -648,6 +650,43 @@ If you use a custom `ServerInterface` and its `connection` event does not
648650
meet this requirement, the `SecureServer` will emit an `error` event and
649651
then close the underlying connection.
650652

653+
#### UnixServer
654+
655+
The `UnixServer` class implements the [`ServerInterface`](#serverinterface) and
656+
is responsible for accepting connections on Unix domain sockets (UDS).
657+
658+
```php
659+
$server = new UnixServer('/tmp/server.sock', $loop);
660+
```
661+
662+
As above, the `$uri` parameter can consist of only a socket path or socket path
663+
prefixed by the `unix://` scheme.
664+
665+
If the given URI appears to be valid, but listening on it fails (such as if the
666+
socket is already in use or the file not accessible etc.), it will throw a
667+
`RuntimeException`:
668+
669+
```php
670+
$first = new UnixServer('/tmp/same.sock', $loop);
671+
672+
// throws RuntimeException because socket is already in use
673+
$second = new UnixServer('/tmp/same.sock', $loop);
674+
```
675+
676+
Whenever a client connects, it will emit a `connection` event with a connection
677+
instance implementing [`ConnectionInterface`](#connectioninterface):
678+
679+
```php
680+
$server->on('connection', function (ConnectionInterface $connection) {
681+
echo 'New connection' . PHP_EOL;
682+
683+
$connection->write('hello there!' . PHP_EOL);
684+
685+
});
686+
```
687+
688+
See also the [`ServerInterface`](#serverinterface) for more details.
689+
651690
#### LimitingServer
652691

653692
The `LimitingServer` decorator wraps a given `ServerInterface` and is responsible

src/UnixServer.php

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<?php
2+
3+
namespace React\Socket;
4+
5+
use Evenement\EventEmitter;
6+
use React\EventLoop\LoopInterface;
7+
use InvalidArgumentException;
8+
use RuntimeException;
9+
10+
/**
11+
* The `UnixServer` class implements the `ServerInterface` and
12+
* is responsible for accepting plaintext connections on unix domain sockets.
13+
*
14+
* ```php
15+
* $server = new UnixServer('unix:///tmp/app.sock', $loop);
16+
* ```
17+
*
18+
* See also the `ServerInterface` for more details.
19+
*
20+
* @see ServerInterface
21+
* @see ConnectionInterface
22+
*/
23+
final class UnixServer extends EventEmitter implements ServerInterface
24+
{
25+
private $master;
26+
private $loop;
27+
private $listening = false;
28+
29+
/**
30+
* Creates a plaintext socket server and starts listening on the given unix socket
31+
*
32+
* This starts accepting new incoming connections on the given address.
33+
* See also the `connection event` documented in the `ServerInterface`
34+
* for more details.
35+
*
36+
* ```php
37+
* $server = new UnixServer('unix:///tmp/app.sock', $loop);
38+
* ```
39+
*
40+
* @param string $path
41+
* @param LoopInterface $loop
42+
* @param array $context
43+
* @throws InvalidArgumentException if the listening address is invalid
44+
* @throws RuntimeException if listening on this address fails (already in use etc.)
45+
*/
46+
public function __construct($path, LoopInterface $loop, array $context = array())
47+
{
48+
$this->loop = $loop;
49+
50+
if (strpos($path, '://') === false) {
51+
$path = 'unix://' . $path;
52+
} elseif (substr($path, 0, 7) !== 'unix://') {
53+
throw new \InvalidArgumentException('Given URI "' . $path . '" is invalid');
54+
}
55+
56+
$this->master = @stream_socket_server(
57+
$path,
58+
$errno,
59+
$errstr,
60+
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
61+
stream_context_create(array('socket' => $context))
62+
);
63+
if (false === $this->master) {
64+
throw new RuntimeException('Failed to listen on unix domain socket "' . $path . '": ' . $errstr, $errno);
65+
}
66+
stream_set_blocking($this->master, 0);
67+
68+
$this->resume();
69+
}
70+
71+
public function getAddress()
72+
{
73+
if (!is_resource($this->master)) {
74+
return null;
75+
}
76+
77+
return 'unix://' . stream_socket_get_name($this->master, false);
78+
}
79+
80+
public function pause()
81+
{
82+
if (!$this->listening) {
83+
return;
84+
}
85+
86+
$this->loop->removeReadStream($this->master);
87+
$this->listening = false;
88+
}
89+
90+
public function resume()
91+
{
92+
if ($this->listening || !is_resource($this->master)) {
93+
return;
94+
}
95+
96+
$that = $this;
97+
$this->loop->addReadStream($this->master, function ($master) use ($that) {
98+
$newSocket = @stream_socket_accept($master);
99+
if (false === $newSocket) {
100+
$that->emit('error', array(new \RuntimeException('Error accepting new connection')));
101+
102+
return;
103+
}
104+
$that->handleConnection($newSocket);
105+
});
106+
$this->listening = true;
107+
}
108+
109+
public function close()
110+
{
111+
if (!is_resource($this->master)) {
112+
return;
113+
}
114+
115+
$this->pause();
116+
fclose($this->master);
117+
$this->removeAllListeners();
118+
}
119+
120+
/** @internal */
121+
public function handleConnection($socket)
122+
{
123+
$connection = new Connection($socket, $this->loop);
124+
$connection->unix = true;
125+
126+
$this->emit('connection', array(
127+
$connection
128+
));
129+
}
130+
}

0 commit comments

Comments
 (0)