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}