UnixServer.php 5.0 KB

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