SocketServerTest.php 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. <?php
  2. namespace React\Tests\Socket;
  3. use React\Promise\Promise;
  4. use React\Socket\ConnectionInterface;
  5. use React\Socket\SocketServer;
  6. use React\Socket\TcpConnector;
  7. use React\Socket\UnixConnector;
  8. class SocketServerTest extends TestCase
  9. {
  10. const TIMEOUT = 0.1;
  11. public function testConstructWithoutLoopAssignsLoopAutomatically()
  12. {
  13. $socket = new SocketServer('127.0.0.1:0');
  14. $socket->close();
  15. $ref = new \ReflectionProperty($socket, 'server');
  16. $ref->setAccessible(true);
  17. $tcp = $ref->getValue($socket);
  18. $ref = new \ReflectionProperty($tcp, 'loop');
  19. $ref->setAccessible(true);
  20. $loop = $ref->getValue($tcp);
  21. $this->assertInstanceOf('React\EventLoop\LoopInterface', $loop);
  22. }
  23. public function testCreateServerWithZeroPortAssignsRandomPort()
  24. {
  25. $socket = new SocketServer('127.0.0.1:0', array());
  26. $this->assertNotEquals(0, $socket->getAddress());
  27. $socket->close();
  28. }
  29. public function testConstructorWithInvalidUriThrows()
  30. {
  31. $this->setExpectedException(
  32. 'InvalidArgumentException',
  33. 'Invalid URI "tcp://invalid URI" given (EINVAL)',
  34. defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
  35. );
  36. new SocketServer('invalid URI');
  37. }
  38. public function testConstructorWithInvalidUriWithPortOnlyThrows()
  39. {
  40. $this->setExpectedException(
  41. 'InvalidArgumentException',
  42. 'Invalid URI given (EINVAL)',
  43. defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
  44. );
  45. new SocketServer('0');
  46. }
  47. public function testConstructorWithInvalidUriWithSchemaAndPortOnlyThrows()
  48. {
  49. $this->setExpectedException(
  50. 'InvalidArgumentException',
  51. 'Invalid URI given (EINVAL)',
  52. defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
  53. );
  54. new SocketServer('tcp://0');
  55. }
  56. public function testConstructorCreatesExpectedTcpServer()
  57. {
  58. $socket = new SocketServer('127.0.0.1:0', array());
  59. $connector = new TcpConnector();
  60. $promise = $connector->connect($socket->getAddress());
  61. $promise->then($this->expectCallableOnce(), $this->expectCallableNever());
  62. $connection = \React\Async\await(\React\Promise\Timer\timeout($connector->connect($socket->getAddress()), self::TIMEOUT));
  63. $socket->close();
  64. $promise->then(function (ConnectionInterface $connection) {
  65. $connection->close();
  66. });
  67. }
  68. public function testConstructorCreatesExpectedUnixServer()
  69. {
  70. if (defined('HHVM_VERSION')) {
  71. $this->markTestSkipped('Not supported on legacy HHVM');
  72. }
  73. if (!in_array('unix', stream_get_transports())) {
  74. $this->markTestSkipped('Unix domain sockets (UDS) not supported on your platform (Windows?)');
  75. }
  76. $socket = new SocketServer($this->getRandomSocketUri(), array());
  77. $connector = new UnixConnector();
  78. $connector->connect($socket->getAddress())
  79. ->then($this->expectCallableOnce(), $this->expectCallableNever());
  80. $connection = \React\Async\await(\React\Promise\Timer\timeout($connector->connect($socket->getAddress()), self::TIMEOUT));
  81. assert($connection instanceof ConnectionInterface);
  82. unlink(str_replace('unix://', '', $connection->getRemoteAddress()));
  83. $connection->close();
  84. $socket->close();
  85. }
  86. public function testConstructorThrowsForExistingUnixPath()
  87. {
  88. if (!in_array('unix', stream_get_transports())) {
  89. $this->markTestSkipped('Unix domain sockets (UDS) not supported on your platform (Windows?)');
  90. }
  91. try {
  92. new SocketServer('unix://' . __FILE__, array());
  93. $this->fail();
  94. } catch (\RuntimeException $e) {
  95. if ($e->getCode() === 0) {
  96. // Zend PHP does not currently report a sane error
  97. $this->assertStringEndsWith('Unknown error', $e->getMessage());
  98. } else {
  99. $this->assertEquals(SOCKET_EADDRINUSE, $e->getCode());
  100. $this->assertStringEndsWith('Address already in use (EADDRINUSE)', $e->getMessage());
  101. }
  102. }
  103. }
  104. public function testConstructWithExistingFileDescriptorReturnsSameAddressAsOriginalSocketForIpv4Socket()
  105. {
  106. if (!is_dir('/dev/fd') || defined('HHVM_VERSION')) {
  107. $this->markTestSkipped('Not supported on your platform');
  108. }
  109. $fd = FdServerTest::getNextFreeFd();
  110. $socket = stream_socket_server('127.0.0.1:0');
  111. $server = new SocketServer('php://fd/' . $fd);
  112. $server->pause();
  113. $this->assertEquals('tcp://' . stream_socket_get_name($socket, false), $server->getAddress());
  114. }
  115. public function testEmitsErrorWhenUnderlyingTcpServerEmitsError()
  116. {
  117. $socket = new SocketServer('127.0.0.1:0', array());
  118. $ref = new \ReflectionProperty($socket, 'server');
  119. $ref->setAccessible(true);
  120. $tcp = $ref->getvalue($socket);
  121. $error = new \RuntimeException();
  122. $socket->on('error', $this->expectCallableOnceWith($error));
  123. $tcp->emit('error', array($error));
  124. $socket->close();
  125. }
  126. public function testEmitsConnectionForNewConnection()
  127. {
  128. $socket = new SocketServer('127.0.0.1:0', array());
  129. $socket->on('connection', $this->expectCallableOnce());
  130. $peer = new Promise(function ($resolve, $reject) use ($socket) {
  131. $socket->on('connection', function () use ($resolve) {
  132. $resolve(null);
  133. });
  134. });
  135. $client = stream_socket_client($socket->getAddress());
  136. \React\Async\await(\React\Promise\Timer\timeout($peer, self::TIMEOUT));
  137. $socket->close();
  138. }
  139. public function testDoesNotEmitConnectionForNewConnectionToPausedServer()
  140. {
  141. $socket = new SocketServer('127.0.0.1:0', array());
  142. $socket->pause();
  143. $socket->on('connection', $this->expectCallableNever());
  144. $client = stream_socket_client($socket->getAddress());
  145. \React\Async\await(\React\Promise\Timer\sleep(0.1));
  146. }
  147. public function testDoesEmitConnectionForNewConnectionToResumedServer()
  148. {
  149. $socket = new SocketServer('127.0.0.1:0', array());
  150. $socket->pause();
  151. $socket->on('connection', $this->expectCallableOnce());
  152. $peer = new Promise(function ($resolve, $reject) use ($socket) {
  153. $socket->on('connection', function () use ($resolve) {
  154. $resolve(null);
  155. });
  156. });
  157. $client = stream_socket_client($socket->getAddress());
  158. $socket->resume();
  159. \React\Async\await(\React\Promise\Timer\timeout($peer, self::TIMEOUT));
  160. $socket->close();
  161. }
  162. public function testDoesNotAllowConnectionToClosedServer()
  163. {
  164. $socket = new SocketServer('127.0.0.1:0', array());
  165. $socket->on('connection', $this->expectCallableNever());
  166. $address = $socket->getAddress();
  167. $socket->close();
  168. $client = @stream_socket_client($address);
  169. $this->assertFalse($client);
  170. }
  171. public function testEmitsConnectionWithInheritedContextOptions()
  172. {
  173. if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.13', '<')) {
  174. // https://3v4l.org/hB4Tc
  175. $this->markTestSkipped('Not supported on legacy HHVM < 3.13');
  176. }
  177. $socket = new SocketServer('127.0.0.1:0', array(
  178. 'tcp' => array(
  179. 'backlog' => 4
  180. )
  181. ));
  182. $peer = new Promise(function ($resolve, $reject) use ($socket) {
  183. $socket->on('connection', function (ConnectionInterface $connection) use ($resolve) {
  184. $resolve(stream_context_get_options($connection->stream));
  185. });
  186. });
  187. $client = stream_socket_client($socket->getAddress());
  188. $all = \React\Async\await(\React\Promise\Timer\timeout($peer, self::TIMEOUT));
  189. $this->assertEquals(array('socket' => array('backlog' => 4)), $all);
  190. $socket->close();
  191. }
  192. public function testDoesNotEmitSecureConnectionForNewPlaintextConnectionThatIsIdle()
  193. {
  194. if (defined('HHVM_VERSION')) {
  195. $this->markTestSkipped('Not supported on legacy HHVM');
  196. }
  197. $socket = new SocketServer('tls://127.0.0.1:0', array(
  198. 'tls' => array(
  199. 'local_cert' => __DIR__ . '/../examples/localhost.pem'
  200. )
  201. ));
  202. $socket->on('connection', $this->expectCallableNever());
  203. $client = stream_socket_client(str_replace('tls://', '', $socket->getAddress()));
  204. \React\Async\await(\React\Promise\Timer\sleep(0.1));
  205. $socket->close();
  206. }
  207. private function getRandomSocketUri()
  208. {
  209. return "unix://" . sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(rand(), true) . '.sock';
  210. }
  211. }