FunctionalTcpServerTest.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. <?php
  2. namespace React\Tests\Socket;
  3. use React\Promise\Promise;
  4. use React\Socket\ConnectionInterface;
  5. use React\Socket\TcpConnector;
  6. use React\Socket\TcpServer;
  7. class FunctionalTcpServerTest extends TestCase
  8. {
  9. const TIMEOUT = 0.1;
  10. public function testEmitsConnectionForNewConnection()
  11. {
  12. $server = new TcpServer(0);
  13. $server->on('connection', $this->expectCallableOnce());
  14. $peer = new Promise(function ($resolve, $reject) use ($server) {
  15. $server->on('connection', function () use ($resolve) {
  16. $resolve(null);
  17. });
  18. });
  19. $connector = new TcpConnector();
  20. $promise = $connector->connect($server->getAddress());
  21. $promise->then($this->expectCallableOnce());
  22. \React\Async\await(\React\Promise\Timer\timeout($peer, self::TIMEOUT));
  23. \React\Async\await(\React\Promise\Timer\sleep(0.0));
  24. $server->close();
  25. $promise->then(function (ConnectionInterface $connection) {
  26. $connection->close();
  27. });
  28. }
  29. public function testEmitsNoConnectionForNewConnectionWhenPaused()
  30. {
  31. $server = new TcpServer(0);
  32. $server->on('connection', $this->expectCallableNever());
  33. $server->pause();
  34. $connector = new TcpConnector();
  35. $promise = $connector->connect($server->getAddress());
  36. $promise->then($this->expectCallableOnce());
  37. \React\Async\await(\React\Promise\Timer\timeout($promise, self::TIMEOUT));
  38. \React\Async\await(\React\Promise\Timer\sleep(0.0));
  39. }
  40. public function testConnectionForNewConnectionWhenResumedAfterPause()
  41. {
  42. $server = new TcpServer(0);
  43. $server->on('connection', $this->expectCallableOnce());
  44. $server->pause();
  45. $server->resume();
  46. $peer = new Promise(function ($resolve, $reject) use ($server) {
  47. $server->on('connection', function () use ($resolve) {
  48. $resolve(null);
  49. });
  50. });
  51. $connector = new TcpConnector();
  52. $promise = $connector->connect($server->getAddress());
  53. $promise->then($this->expectCallableOnce());
  54. \React\Async\await(\React\Promise\Timer\timeout($peer, self::TIMEOUT));
  55. \React\Async\await(\React\Promise\Timer\sleep(0.0));
  56. $server->close();
  57. $promise->then(function (ConnectionInterface $connection) {
  58. $connection->close();
  59. });
  60. }
  61. public function testEmitsConnectionWithRemoteIp()
  62. {
  63. $server = new TcpServer(0);
  64. $peer = new Promise(function ($resolve, $reject) use ($server) {
  65. $server->on('connection', function (ConnectionInterface $connection) use ($resolve) {
  66. $resolve($connection->getRemoteAddress());
  67. });
  68. });
  69. $connector = new TcpConnector();
  70. $promise = $connector->connect($server->getAddress());
  71. $promise->then($this->expectCallableOnce());
  72. $peer = \React\Async\await(\React\Promise\Timer\timeout($peer, self::TIMEOUT));
  73. \React\Async\await(\React\Promise\Timer\sleep(0.0));
  74. $this->assertContainsString('127.0.0.1:', $peer);
  75. $server->close();
  76. $promise->then(function (ConnectionInterface $connection) {
  77. $connection->close();
  78. });
  79. }
  80. public function testEmitsConnectionWithLocalIp()
  81. {
  82. $server = new TcpServer(0);
  83. $peer = new Promise(function ($resolve, $reject) use ($server) {
  84. $server->on('connection', function (ConnectionInterface $connection) use ($resolve) {
  85. $resolve($connection->getLocalAddress());
  86. });
  87. });
  88. $connector = new TcpConnector();
  89. $promise = $connector->connect($server->getAddress());
  90. $promise->then($this->expectCallableOnce());
  91. $promise->then($this->expectCallableOnce());
  92. $local = \React\Async\await(\React\Promise\Timer\timeout($peer, self::TIMEOUT));
  93. \React\Async\await(\React\Promise\Timer\sleep(0.0));
  94. $this->assertContainsString('127.0.0.1:', $local);
  95. $this->assertEquals($server->getAddress(), $local);
  96. $server->close();
  97. $promise->then(function (ConnectionInterface $connection) {
  98. $connection->close();
  99. });
  100. }
  101. public function testEmitsConnectionWithLocalIpDespiteListeningOnAll()
  102. {
  103. if (DIRECTORY_SEPARATOR === '\\') {
  104. $this->markTestSkipped('Skipping on Windows due to default firewall rules');
  105. }
  106. $server = new TcpServer('0.0.0.0:0');
  107. $peer = new Promise(function ($resolve, $reject) use ($server) {
  108. $server->on('connection', function (ConnectionInterface $connection) use ($resolve) {
  109. $resolve($connection->getLocalAddress());
  110. });
  111. });
  112. $connector = new TcpConnector();
  113. $promise = $connector->connect($server->getAddress());
  114. $promise->then($this->expectCallableOnce());
  115. $local = \React\Async\await(\React\Promise\Timer\timeout($peer, self::TIMEOUT));
  116. \React\Async\await(\React\Promise\Timer\sleep(0.0));
  117. $this->assertContainsString('127.0.0.1:', $local);
  118. $server->close();
  119. $promise->then(function (ConnectionInterface $connection) {
  120. $connection->close();
  121. });
  122. }
  123. public function testEmitsConnectionWithRemoteIpAfterConnectionIsClosedByPeer()
  124. {
  125. $server = new TcpServer(0);
  126. $peer = new Promise(function ($resolve, $reject) use ($server) {
  127. $server->on('connection', function (ConnectionInterface $connection) use ($resolve) {
  128. $connection->on('close', function () use ($connection, $resolve) {
  129. $resolve($connection->getRemoteAddress());
  130. });
  131. });
  132. });
  133. $connector = new TcpConnector();
  134. $connector->connect($server->getAddress())->then(function (ConnectionInterface $connection) {
  135. $connection->end();
  136. });
  137. $peer = \React\Async\await(\React\Promise\Timer\timeout($peer, self::TIMEOUT));
  138. $this->assertContainsString('127.0.0.1:', $peer);
  139. $server->close();
  140. }
  141. public function testEmitsConnectionWithRemoteNullAddressAfterConnectionIsClosedByServer()
  142. {
  143. $server = new TcpServer(0);
  144. $peer = new Promise(function ($resolve, $reject) use ($server) {
  145. $server->on('connection', function (ConnectionInterface $connection) use ($resolve) {
  146. $connection->close();
  147. $resolve($connection->getRemoteAddress());
  148. });
  149. });
  150. $connector = new TcpConnector();
  151. $promise = $connector->connect($server->getAddress());
  152. $promise->then($this->expectCallableOnce());
  153. $peer = \React\Async\await(\React\Promise\Timer\timeout($peer, self::TIMEOUT));
  154. \React\Async\await(\React\Promise\Timer\sleep(0.0));
  155. $this->assertNull($peer);
  156. $server->close();
  157. }
  158. public function testEmitsConnectionEvenIfClientConnectionIsCancelled()
  159. {
  160. if (PHP_OS !== 'Linux') {
  161. $this->markTestSkipped('Linux only (OS is ' . PHP_OS . ')');
  162. }
  163. $server = new TcpServer(0);
  164. $server->on('connection', $this->expectCallableOnce());
  165. $peer = new Promise(function ($resolve, $reject) use ($server) {
  166. $server->on('connection', function () use ($resolve) {
  167. $resolve(null);
  168. });
  169. });
  170. $connector = new TcpConnector();
  171. $promise = $connector->connect($server->getAddress());
  172. $promise->cancel();
  173. $promise->then(null, $this->expectCallableOnce());
  174. \React\Async\await(\React\Promise\Timer\timeout($peer, self::TIMEOUT));
  175. $server->close();
  176. }
  177. public function testEmitsConnectionForNewIpv6Connection()
  178. {
  179. try {
  180. $server = new TcpServer('[::1]:0');
  181. } catch (\RuntimeException $e) {
  182. $this->markTestSkipped('Unable to start IPv6 server socket (not available on your platform?)');
  183. }
  184. $server->on('connection', $this->expectCallableOnce());
  185. $peer = new Promise(function ($resolve, $reject) use ($server) {
  186. $server->on('connection', function () use ($resolve) {
  187. $resolve(null);
  188. });
  189. });
  190. $connector = new TcpConnector();
  191. $promise = $connector->connect($server->getAddress());
  192. $promise->then($this->expectCallableOnce());
  193. \React\Async\await(\React\Promise\Timer\timeout($peer, self::TIMEOUT));
  194. \React\Async\await(\React\Promise\Timer\sleep(0.0));
  195. $server->close();
  196. $promise->then(function (ConnectionInterface $connection) {
  197. $connection->close();
  198. });
  199. }
  200. public function testEmitsConnectionWithRemoteIpv6()
  201. {
  202. try {
  203. $server = new TcpServer('[::1]:0');
  204. } catch (\RuntimeException $e) {
  205. $this->markTestSkipped('Unable to start IPv6 server socket (not available on your platform?)');
  206. }
  207. $peer = new Promise(function ($resolve, $reject) use ($server) {
  208. $server->on('connection', function (ConnectionInterface $connection) use ($resolve) {
  209. $resolve($connection->getRemoteAddress());
  210. });
  211. });
  212. $connector = new TcpConnector();
  213. $promise = $connector->connect($server->getAddress());
  214. $promise->then($this->expectCallableOnce());
  215. $peer = \React\Async\await(\React\Promise\Timer\timeout($peer, self::TIMEOUT));
  216. \React\Async\await(\React\Promise\Timer\sleep(0.0));
  217. $this->assertContainsString('[::1]:', $peer);
  218. $server->close();
  219. $promise->then(function (ConnectionInterface $connection) {
  220. $connection->close();
  221. });
  222. }
  223. public function testEmitsConnectionWithLocalIpv6()
  224. {
  225. try {
  226. $server = new TcpServer('[::1]:0');
  227. } catch (\RuntimeException $e) {
  228. $this->markTestSkipped('Unable to start IPv6 server socket (not available on your platform?)');
  229. }
  230. $peer = new Promise(function ($resolve, $reject) use ($server) {
  231. $server->on('connection', function (ConnectionInterface $connection) use ($resolve) {
  232. $resolve($connection->getLocalAddress());
  233. });
  234. });
  235. $connector = new TcpConnector();
  236. $promise = $connector->connect($server->getAddress());
  237. $promise->then($this->expectCallableOnce());
  238. $local = \React\Async\await(\React\Promise\Timer\timeout($peer, self::TIMEOUT));
  239. \React\Async\await(\React\Promise\Timer\sleep(0.0));
  240. $this->assertContainsString('[::1]:', $local);
  241. $this->assertEquals($server->getAddress(), $local);
  242. $server->close();
  243. $promise->then(function (ConnectionInterface $connection) {
  244. $connection->close();
  245. });
  246. }
  247. public function testServerPassesContextOptionsToSocket()
  248. {
  249. $server = new TcpServer(0, null, array(
  250. 'backlog' => 4
  251. ));
  252. $ref = new \ReflectionProperty($server, 'master');
  253. $ref->setAccessible(true);
  254. $socket = $ref->getValue($server);
  255. $context = stream_context_get_options($socket);
  256. $this->assertEquals(array('socket' => array('backlog' => 4)), $context);
  257. $server->close();
  258. }
  259. public function testServerPassesDefaultBacklogSizeViaContextOptionsToSocket()
  260. {
  261. $server = new TcpServer(0);
  262. $ref = new \ReflectionProperty($server, 'master');
  263. $ref->setAccessible(true);
  264. $socket = $ref->getValue($server);
  265. $context = stream_context_get_options($socket);
  266. $this->assertEquals(array('socket' => array('backlog' => 511)), $context);
  267. $server->close();
  268. }
  269. public function testEmitsConnectionWithInheritedContextOptions()
  270. {
  271. if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.13', '<')) {
  272. // https://3v4l.org/hB4Tc
  273. $this->markTestSkipped('Not supported on legacy HHVM < 3.13');
  274. }
  275. $server = new TcpServer(0, null, array(
  276. 'backlog' => 4
  277. ));
  278. $peer = new Promise(function ($resolve, $reject) use ($server) {
  279. $server->on('connection', function (ConnectionInterface $connection) use ($resolve) {
  280. $resolve(stream_context_get_options($connection->stream));
  281. });
  282. });
  283. $connector = new TcpConnector();
  284. $promise = $connector->connect($server->getAddress());
  285. $promise->then($this->expectCallableOnce());
  286. $all = \React\Async\await(\React\Promise\Timer\timeout($peer, self::TIMEOUT));
  287. \React\Async\await(\React\Promise\Timer\sleep(0.0));
  288. $this->assertEquals(array('socket' => array('backlog' => 4)), $all);
  289. $server->close();
  290. $promise->then(function (ConnectionInterface $connection) {
  291. $connection->close();
  292. });
  293. }
  294. public function testFailsToListenOnInvalidUri()
  295. {
  296. $this->setExpectedException(
  297. 'InvalidArgumentException',
  298. 'Invalid URI "tcp://///" given (EINVAL)',
  299. defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
  300. );
  301. new TcpServer('///');
  302. }
  303. public function testFailsToListenOnUriWithoutPort()
  304. {
  305. $this->setExpectedException(
  306. 'InvalidArgumentException',
  307. 'Invalid URI "tcp://127.0.0.1" given (EINVAL)',
  308. defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
  309. );
  310. new TcpServer('127.0.0.1');
  311. }
  312. public function testFailsToListenOnUriWithWrongScheme()
  313. {
  314. $this->setExpectedException(
  315. 'InvalidArgumentException',
  316. 'Invalid URI "udp://127.0.0.1:0" given (EINVAL)',
  317. defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
  318. );
  319. new TcpServer('udp://127.0.0.1:0');
  320. }
  321. public function testFailsToListenOnUriWIthHostname()
  322. {
  323. $this->setExpectedException(
  324. 'InvalidArgumentException',
  325. 'Given URI "tcp://localhost:8080" does not contain a valid host IP (EINVAL)',
  326. defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
  327. );
  328. new TcpServer('localhost:8080');
  329. }
  330. }