friendship ended with social-app. php is my new best friend
1<?php
2
3namespace React\Socket;
4
5use Evenement\EventEmitter;
6use React\EventLoop\Loop;
7use React\EventLoop\LoopInterface;
8use InvalidArgumentException;
9use RuntimeException;
10
11/**
12 * The `UnixServer` class implements the `ServerInterface` and
13 * is responsible for accepting plaintext connections on unix domain sockets.
14 *
15 * ```php
16 * $server = new React\Socket\UnixServer('unix:///tmp/app.sock');
17 * ```
18 *
19 * See also the `ServerInterface` for more details.
20 *
21 * @see ServerInterface
22 * @see ConnectionInterface
23 */
24final class UnixServer extends EventEmitter implements ServerInterface
25{
26 private $master;
27 private $loop;
28 private $listening = false;
29
30 /**
31 * Creates a plaintext socket server and starts listening on the given unix socket
32 *
33 * This starts accepting new incoming connections on the given address.
34 * See also the `connection event` documented in the `ServerInterface`
35 * for more details.
36 *
37 * ```php
38 * $server = new React\Socket\UnixServer('unix:///tmp/app.sock');
39 * ```
40 *
41 * This class takes an optional `LoopInterface|null $loop` parameter that can be used to
42 * pass the event loop instance to use for this object. You can use a `null` value
43 * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
44 * This value SHOULD NOT be given unless you're sure you want to explicitly use a
45 * given event loop instance.
46 *
47 * @param string $path
48 * @param ?LoopInterface $loop
49 * @param array $context
50 * @throws InvalidArgumentException if the listening address is invalid
51 * @throws RuntimeException if listening on this address fails (already in use etc.)
52 */
53 public function __construct($path, $loop = null, array $context = array())
54 {
55 if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
56 throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
57 }
58
59 $this->loop = $loop ?: Loop::get();
60
61 if (\strpos($path, '://') === false) {
62 $path = 'unix://' . $path;
63 } elseif (\substr($path, 0, 7) !== 'unix://') {
64 throw new \InvalidArgumentException(
65 'Given URI "' . $path . '" is invalid (EINVAL)',
66 \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
67 );
68 }
69
70 $errno = 0;
71 $errstr = '';
72 \set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
73 // PHP does not seem to report errno/errstr for Unix domain sockets (UDS) right now.
74 // This only applies to UDS server sockets, see also https://3v4l.org/NAhpr.
75 // Parse PHP warning message containing unknown error, HHVM reports proper info at least.
76 if (\preg_match('/\(([^\)]+)\)|\[(\d+)\]: (.*)/', $error, $match)) {
77 $errstr = isset($match[3]) ? $match['3'] : $match[1];
78 $errno = isset($match[2]) ? (int)$match[2] : 0;
79 }
80 });
81
82 $this->master = \stream_socket_server(
83 $path,
84 $errno,
85 $errstr,
86 \STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN,
87 \stream_context_create(array('socket' => $context))
88 );
89
90 \restore_error_handler();
91
92 if (false === $this->master) {
93 throw new \RuntimeException(
94 'Failed to listen on Unix domain socket "' . $path . '": ' . $errstr . SocketServer::errconst($errno),
95 $errno
96 );
97 }
98 \stream_set_blocking($this->master, 0);
99
100 $this->resume();
101 }
102
103 public function getAddress()
104 {
105 if (!\is_resource($this->master)) {
106 return null;
107 }
108
109 return 'unix://' . \stream_socket_get_name($this->master, false);
110 }
111
112 public function pause()
113 {
114 if (!$this->listening) {
115 return;
116 }
117
118 $this->loop->removeReadStream($this->master);
119 $this->listening = false;
120 }
121
122 public function resume()
123 {
124 if ($this->listening || !is_resource($this->master)) {
125 return;
126 }
127
128 $that = $this;
129 $this->loop->addReadStream($this->master, function ($master) use ($that) {
130 try {
131 $newSocket = SocketServer::accept($master);
132 } catch (\RuntimeException $e) {
133 $that->emit('error', array($e));
134 return;
135 }
136 $that->handleConnection($newSocket);
137 });
138 $this->listening = true;
139 }
140
141 public function close()
142 {
143 if (!\is_resource($this->master)) {
144 return;
145 }
146
147 $this->pause();
148 \fclose($this->master);
149 $this->removeAllListeners();
150 }
151
152 /** @internal */
153 public function handleConnection($socket)
154 {
155 $connection = new Connection($socket, $this->loop);
156 $connection->unix = true;
157
158 $this->emit('connection', array(
159 $connection
160 ));
161 }
162}