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}