friendship ended with social-app. php is my new best friend
1<?php
2
3namespace React\Socket;
4
5use Evenement\EventEmitter;
6use React\EventLoop\LoopInterface;
7
8final class SocketServer extends EventEmitter implements ServerInterface
9{
10 private $server;
11
12 /**
13 * The `SocketServer` class is the main class in this package that implements the `ServerInterface` and
14 * allows you to accept incoming streaming connections, such as plaintext TCP/IP or secure TLS connection streams.
15 *
16 * ```php
17 * $socket = new React\Socket\SocketServer('127.0.0.1:0');
18 * $socket = new React\Socket\SocketServer('127.0.0.1:8000');
19 * $socket = new React\Socket\SocketServer('127.0.0.1:8000', $context);
20 * ```
21 *
22 * This class takes an optional `LoopInterface|null $loop` parameter that can be used to
23 * pass the event loop instance to use for this object. You can use a `null` value
24 * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
25 * This value SHOULD NOT be given unless you're sure you want to explicitly use a
26 * given event loop instance.
27 *
28 * @param string $uri
29 * @param array $context
30 * @param ?LoopInterface $loop
31 * @throws \InvalidArgumentException if the listening address is invalid
32 * @throws \RuntimeException if listening on this address fails (already in use etc.)
33 */
34 public function __construct($uri, array $context = array(), $loop = null)
35 {
36 if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
37 throw new \InvalidArgumentException('Argument #3 ($loop) expected null|React\EventLoop\LoopInterface');
38 }
39
40 // apply default options if not explicitly given
41 $context += array(
42 'tcp' => array(),
43 'tls' => array(),
44 'unix' => array()
45 );
46
47 $scheme = 'tcp';
48 $pos = \strpos($uri, '://');
49 if ($pos !== false) {
50 $scheme = \substr($uri, 0, $pos);
51 }
52
53 if ($scheme === 'unix') {
54 $server = new UnixServer($uri, $loop, $context['unix']);
55 } elseif ($scheme === 'php') {
56 $server = new FdServer($uri, $loop);
57 } else {
58 if (preg_match('#^(?:\w+://)?\d+$#', $uri)) {
59 throw new \InvalidArgumentException(
60 'Invalid URI given (EINVAL)',
61 \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
62 );
63 }
64
65 $server = new TcpServer(str_replace('tls://', '', $uri), $loop, $context['tcp']);
66
67 if ($scheme === 'tls') {
68 $server = new SecureServer($server, $loop, $context['tls']);
69 }
70 }
71
72 $this->server = $server;
73
74 $that = $this;
75 $server->on('connection', function (ConnectionInterface $conn) use ($that) {
76 $that->emit('connection', array($conn));
77 });
78 $server->on('error', function (\Exception $error) use ($that) {
79 $that->emit('error', array($error));
80 });
81 }
82
83 public function getAddress()
84 {
85 return $this->server->getAddress();
86 }
87
88 public function pause()
89 {
90 $this->server->pause();
91 }
92
93 public function resume()
94 {
95 $this->server->resume();
96 }
97
98 public function close()
99 {
100 $this->server->close();
101 }
102
103 /**
104 * [internal] Internal helper method to accept new connection from given server socket
105 *
106 * @param resource $socket server socket to accept connection from
107 * @return resource new client socket if any
108 * @throws \RuntimeException if accepting fails
109 * @internal
110 */
111 public static function accept($socket)
112 {
113 $errno = 0;
114 $errstr = '';
115 \set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
116 // Match errstr from PHP's warning message.
117 // stream_socket_accept(): accept failed: Connection timed out
118 $errstr = \preg_replace('#.*: #', '', $error);
119 $errno = SocketServer::errno($errstr);
120 });
121
122 $newSocket = \stream_socket_accept($socket, 0);
123
124 \restore_error_handler();
125
126 if (false === $newSocket) {
127 throw new \RuntimeException(
128 'Unable to accept new connection: ' . $errstr . self::errconst($errno),
129 $errno
130 );
131 }
132
133 return $newSocket;
134 }
135
136 /**
137 * [Internal] Returns errno value for given errstr
138 *
139 * The errno and errstr values describes the type of error that has been
140 * encountered. This method tries to look up the given errstr and find a
141 * matching errno value which can be useful to provide more context to error
142 * messages. It goes through the list of known errno constants when either
143 * `ext-sockets`, `ext-posix` or `ext-pcntl` is available to find an errno
144 * matching the given errstr.
145 *
146 * @param string $errstr
147 * @return int errno value (e.g. value of `SOCKET_ECONNREFUSED`) or 0 if not found
148 * @internal
149 * @copyright Copyright (c) 2023 Christian Lück, taken from https://github.com/clue/errno with permission
150 * @codeCoverageIgnore
151 */
152 public static function errno($errstr)
153 {
154 // PHP defines the required `strerror()` function through either `ext-sockets`, `ext-posix` or `ext-pcntl`
155 $strerror = \function_exists('socket_strerror') ? 'socket_strerror' : (\function_exists('posix_strerror') ? 'posix_strerror' : (\function_exists('pcntl_strerror') ? 'pcntl_strerror' : null));
156 if ($strerror !== null) {
157 assert(\is_string($strerror) && \is_callable($strerror));
158
159 // PHP defines most useful errno constants like `ECONNREFUSED` through constants in `ext-sockets` like `SOCKET_ECONNREFUSED`
160 // PHP also defines a hand full of errno constants like `EMFILE` through constants in `ext-pcntl` like `PCNTL_EMFILE`
161 // go through list of all defined constants like `SOCKET_E*` and `PCNTL_E*` and see if they match the given `$errstr`
162 foreach (\get_defined_constants(false) as $name => $value) {
163 if (\is_int($value) && (\strpos($name, 'SOCKET_E') === 0 || \strpos($name, 'PCNTL_E') === 0) && $strerror($value) === $errstr) {
164 return $value;
165 }
166 }
167
168 // if we reach this, no matching errno constant could be found (unlikely when `ext-sockets` is available)
169 // go through list of all possible errno values from 1 to `MAX_ERRNO` and see if they match the given `$errstr`
170 for ($errno = 1, $max = \defined('MAX_ERRNO') ? \MAX_ERRNO : 4095; $errno <= $max; ++$errno) {
171 if ($strerror($errno) === $errstr) {
172 return $errno;
173 }
174 }
175 }
176
177 // if we reach this, no matching errno value could be found (unlikely when either `ext-sockets`, `ext-posix` or `ext-pcntl` is available)
178 return 0;
179 }
180
181 /**
182 * [Internal] Returns errno constant name for given errno value
183 *
184 * The errno value describes the type of error that has been encountered.
185 * This method tries to look up the given errno value and find a matching
186 * errno constant name which can be useful to provide more context and more
187 * descriptive error messages. It goes through the list of known errno
188 * constants when either `ext-sockets` or `ext-pcntl` is available to find
189 * the matching errno constant name.
190 *
191 * Because this method is used to append more context to error messages, the
192 * constant name will be prefixed with a space and put between parenthesis
193 * when found.
194 *
195 * @param int $errno
196 * @return string e.g. ` (ECONNREFUSED)` or empty string if no matching const for the given errno could be found
197 * @internal
198 * @copyright Copyright (c) 2023 Christian Lück, taken from https://github.com/clue/errno with permission
199 * @codeCoverageIgnore
200 */
201 public static function errconst($errno)
202 {
203 // PHP defines most useful errno constants like `ECONNREFUSED` through constants in `ext-sockets` like `SOCKET_ECONNREFUSED`
204 // PHP also defines a hand full of errno constants like `EMFILE` through constants in `ext-pcntl` like `PCNTL_EMFILE`
205 // go through list of all defined constants like `SOCKET_E*` and `PCNTL_E*` and see if they match the given `$errno`
206 foreach (\get_defined_constants(false) as $name => $value) {
207 if ($value === $errno && (\strpos($name, 'SOCKET_E') === 0 || \strpos($name, 'PCNTL_E') === 0)) {
208 return ' (' . \substr($name, \strpos($name, '_') + 1) . ')';
209 }
210 }
211
212 // if we reach this, no matching errno constant could be found (unlikely when `ext-sockets` is available)
213 return '';
214 }
215}