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 `TcpServer` class implements the `ServerInterface` and 13 * is responsible for accepting plaintext TCP/IP connections. 14 * 15 * ```php 16 * $server = new React\Socket\TcpServer(8080); 17 * ``` 18 * 19 * Whenever a client connects, it will emit a `connection` event with a connection 20 * instance implementing `ConnectionInterface`: 21 * 22 * ```php 23 * $server->on('connection', function (React\Socket\ConnectionInterface $connection) { 24 * echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL; 25 * $connection->write('hello there!' . PHP_EOL); 26 * … 27 * }); 28 * ``` 29 * 30 * See also the `ServerInterface` for more details. 31 * 32 * @see ServerInterface 33 * @see ConnectionInterface 34 */ 35final class TcpServer extends EventEmitter implements ServerInterface 36{ 37 private $master; 38 private $loop; 39 private $listening = false; 40 41 /** 42 * Creates a plaintext TCP/IP socket server and starts listening on the given address 43 * 44 * This starts accepting new incoming connections on the given address. 45 * See also the `connection event` documented in the `ServerInterface` 46 * for more details. 47 * 48 * ```php 49 * $server = new React\Socket\TcpServer(8080); 50 * ``` 51 * 52 * As above, the `$uri` parameter can consist of only a port, in which case the 53 * server will default to listening on the localhost address `127.0.0.1`, 54 * which means it will not be reachable from outside of this system. 55 * 56 * In order to use a random port assignment, you can use the port `0`: 57 * 58 * ```php 59 * $server = new React\Socket\TcpServer(0); 60 * $address = $server->getAddress(); 61 * ``` 62 * 63 * In order to change the host the socket is listening on, you can provide an IP 64 * address through the first parameter provided to the constructor, optionally 65 * preceded by the `tcp://` scheme: 66 * 67 * ```php 68 * $server = new React\Socket\TcpServer('192.168.0.1:8080'); 69 * ``` 70 * 71 * If you want to listen on an IPv6 address, you MUST enclose the host in square 72 * brackets: 73 * 74 * ```php 75 * $server = new React\Socket\TcpServer('[::1]:8080'); 76 * ``` 77 * 78 * If the given URI is invalid, does not contain a port, any other scheme or if it 79 * contains a hostname, it will throw an `InvalidArgumentException`: 80 * 81 * ```php 82 * // throws InvalidArgumentException due to missing port 83 * $server = new React\Socket\TcpServer('127.0.0.1'); 84 * ``` 85 * 86 * If the given URI appears to be valid, but listening on it fails (such as if port 87 * is already in use or port below 1024 may require root access etc.), it will 88 * throw a `RuntimeException`: 89 * 90 * ```php 91 * $first = new React\Socket\TcpServer(8080); 92 * 93 * // throws RuntimeException because port is already in use 94 * $second = new React\Socket\TcpServer(8080); 95 * ``` 96 * 97 * Note that these error conditions may vary depending on your system and/or 98 * configuration. 99 * See the exception message and code for more details about the actual error 100 * condition. 101 * 102 * This class takes an optional `LoopInterface|null $loop` parameter that can be used to 103 * pass the event loop instance to use for this object. You can use a `null` value 104 * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). 105 * This value SHOULD NOT be given unless you're sure you want to explicitly use a 106 * given event loop instance. 107 * 108 * Optionally, you can specify [socket context options](https://www.php.net/manual/en/context.socket.php) 109 * for the underlying stream socket resource like this: 110 * 111 * ```php 112 * $server = new React\Socket\TcpServer('[::1]:8080', null, array( 113 * 'backlog' => 200, 114 * 'so_reuseport' => true, 115 * 'ipv6_v6only' => true 116 * )); 117 * ``` 118 * 119 * Note that available [socket context options](https://www.php.net/manual/en/context.socket.php), 120 * their defaults and effects of changing these may vary depending on your system 121 * and/or PHP version. 122 * Passing unknown context options has no effect. 123 * The `backlog` context option defaults to `511` unless given explicitly. 124 * 125 * @param string|int $uri 126 * @param ?LoopInterface $loop 127 * @param array $context 128 * @throws InvalidArgumentException if the listening address is invalid 129 * @throws RuntimeException if listening on this address fails (already in use etc.) 130 */ 131 public function __construct($uri, $loop = null, array $context = array()) 132 { 133 if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1 134 throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface'); 135 } 136 137 $this->loop = $loop ?: Loop::get(); 138 139 // a single port has been given => assume localhost 140 if ((string)(int)$uri === (string)$uri) { 141 $uri = '127.0.0.1:' . $uri; 142 } 143 144 // assume default scheme if none has been given 145 if (\strpos($uri, '://') === false) { 146 $uri = 'tcp://' . $uri; 147 } 148 149 // parse_url() does not accept null ports (random port assignment) => manually remove 150 if (\substr($uri, -2) === ':0') { 151 $parts = \parse_url(\substr($uri, 0, -2)); 152 if ($parts) { 153 $parts['port'] = 0; 154 } 155 } else { 156 $parts = \parse_url($uri); 157 } 158 159 // ensure URI contains TCP scheme, host and port 160 if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') { 161 throw new \InvalidArgumentException( 162 'Invalid URI "' . $uri . '" given (EINVAL)', 163 \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22) 164 ); 165 } 166 167 if (@\inet_pton(\trim($parts['host'], '[]')) === false) { 168 throw new \InvalidArgumentException( 169 'Given URI "' . $uri . '" does not contain a valid host IP (EINVAL)', 170 \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22) 171 ); 172 } 173 174 $this->master = @\stream_socket_server( 175 $uri, 176 $errno, 177 $errstr, 178 \STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN, 179 \stream_context_create(array('socket' => $context + array('backlog' => 511))) 180 ); 181 if (false === $this->master) { 182 if ($errno === 0) { 183 // PHP does not seem to report errno, so match errno from errstr 184 // @link https://3v4l.org/3qOBl 185 $errno = SocketServer::errno($errstr); 186 } 187 188 throw new \RuntimeException( 189 'Failed to listen on "' . $uri . '": ' . $errstr . SocketServer::errconst($errno), 190 $errno 191 ); 192 } 193 \stream_set_blocking($this->master, false); 194 195 $this->resume(); 196 } 197 198 public function getAddress() 199 { 200 if (!\is_resource($this->master)) { 201 return null; 202 } 203 204 $address = \stream_socket_get_name($this->master, false); 205 206 // check if this is an IPv6 address which includes multiple colons but no square brackets 207 $pos = \strrpos($address, ':'); 208 if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') { 209 $address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore 210 } 211 212 return 'tcp://' . $address; 213 } 214 215 public function pause() 216 { 217 if (!$this->listening) { 218 return; 219 } 220 221 $this->loop->removeReadStream($this->master); 222 $this->listening = false; 223 } 224 225 public function resume() 226 { 227 if ($this->listening || !\is_resource($this->master)) { 228 return; 229 } 230 231 $that = $this; 232 $this->loop->addReadStream($this->master, function ($master) use ($that) { 233 try { 234 $newSocket = SocketServer::accept($master); 235 } catch (\RuntimeException $e) { 236 $that->emit('error', array($e)); 237 return; 238 } 239 $that->handleConnection($newSocket); 240 }); 241 $this->listening = true; 242 } 243 244 public function close() 245 { 246 if (!\is_resource($this->master)) { 247 return; 248 } 249 250 $this->pause(); 251 \fclose($this->master); 252 $this->removeAllListeners(); 253 } 254 255 /** @internal */ 256 public function handleConnection($socket) 257 { 258 $this->emit('connection', array( 259 new Connection($socket, $this->loop) 260 )); 261 } 262}