IntegrationTest.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. <?php
  2. namespace React\Tests\Socket;
  3. use React\Dns\Resolver\Factory as ResolverFactory;
  4. use React\EventLoop\Loop;
  5. use React\Socket\ConnectionInterface;
  6. use React\Socket\Connector;
  7. use React\Socket\DnsConnector;
  8. use React\Socket\SecureConnector;
  9. use React\Socket\TcpConnector;
  10. /** @group internet */
  11. class IntegrationTest extends TestCase
  12. {
  13. const TIMEOUT = 5.0;
  14. /** @test */
  15. public function gettingStuffFromGoogleShouldWork()
  16. {
  17. $connector = new Connector(array());
  18. $conn = \React\Async\await($connector->connect('google.com:80'));
  19. assert($conn instanceof ConnectionInterface);
  20. $this->assertContainsString(':80', $conn->getRemoteAddress());
  21. $this->assertNotEquals('google.com:80', $conn->getRemoteAddress());
  22. $conn->write("GET / HTTP/1.0\r\n\r\n");
  23. $response = $this->buffer($conn, self::TIMEOUT);
  24. assert(!$conn->isReadable());
  25. $this->assertMatchesRegExp('#^HTTP/1\.0#', $response);
  26. }
  27. /** @test */
  28. public function gettingEncryptedStuffFromGoogleShouldWork()
  29. {
  30. if (defined('HHVM_VERSION')) {
  31. $this->markTestSkipped('Not supported on legacy HHVM');
  32. }
  33. $secureConnector = new Connector(array());
  34. $conn = \React\Async\await($secureConnector->connect('tls://google.com:443'));
  35. assert($conn instanceof ConnectionInterface);
  36. $conn->write("GET / HTTP/1.0\r\n\r\n");
  37. $response = $this->buffer($conn, self::TIMEOUT);
  38. assert(!$conn->isReadable());
  39. $this->assertMatchesRegExp('#^HTTP/1\.0#', $response);
  40. }
  41. /** @test */
  42. public function gettingEncryptedStuffFromGoogleShouldWorkIfHostIsResolvedFirst()
  43. {
  44. if (defined('HHVM_VERSION')) {
  45. $this->markTestSkipped('Not supported on legacy HHVM');
  46. }
  47. $factory = new ResolverFactory();
  48. $dns = $factory->create('8.8.8.8');
  49. $connector = new DnsConnector(
  50. new SecureConnector(
  51. new TcpConnector()
  52. ),
  53. $dns
  54. );
  55. $conn = \React\Async\await($connector->connect('google.com:443'));
  56. assert($conn instanceof ConnectionInterface);
  57. $conn->write("GET / HTTP/1.0\r\n\r\n");
  58. $response = $this->buffer($conn, self::TIMEOUT);
  59. assert(!$conn->isReadable());
  60. $this->assertMatchesRegExp('#^HTTP/1\.0#', $response);
  61. }
  62. /** @test */
  63. public function gettingPlaintextStuffFromEncryptedGoogleShouldNotWork()
  64. {
  65. $connector = new Connector(array());
  66. $conn = \React\Async\await($connector->connect('google.com:443'));
  67. assert($conn instanceof ConnectionInterface);
  68. $this->assertContainsString(':443', $conn->getRemoteAddress());
  69. $this->assertNotEquals('google.com:443', $conn->getRemoteAddress());
  70. $conn->write("GET / HTTP/1.0\r\n\r\n");
  71. $response = $this->buffer($conn, self::TIMEOUT);
  72. assert(!$conn->isReadable());
  73. $this->assertDoesNotMatchRegExp('#^HTTP/1\.0#', $response);
  74. }
  75. public function testConnectingFailsIfConnectorUsesInvalidDnsResolverAddress()
  76. {
  77. if (PHP_OS === 'Darwin') {
  78. $this->markTestSkipped('Skipped on macOS due to a bug in reactphp/dns (solved in reactphp/dns#171)');
  79. }
  80. $factory = new ResolverFactory();
  81. $dns = $factory->create('255.255.255.255');
  82. $connector = new Connector(array(
  83. 'dns' => $dns
  84. ));
  85. $this->setExpectedException('RuntimeException');
  86. \React\Async\await(\React\Promise\Timer\timeout($connector->connect('google.com:80'), self::TIMEOUT));
  87. }
  88. public function testCancellingPendingConnectionWithoutTimeoutShouldNotCreateAnyGarbageReferences()
  89. {
  90. if (class_exists('React\Promise\When')) {
  91. $this->markTestSkipped('Not supported on legacy Promise v1 API');
  92. }
  93. $connector = new Connector(array('timeout' => false));
  94. while (gc_collect_cycles()) {
  95. // collect all garbage cycles
  96. }
  97. $promise = $connector->connect('8.8.8.8:80');
  98. $promise->cancel();
  99. unset($promise);
  100. $this->assertEquals(0, gc_collect_cycles());
  101. }
  102. public function testCancellingPendingConnectionShouldNotCreateAnyGarbageReferences()
  103. {
  104. if (class_exists('React\Promise\When')) {
  105. $this->markTestSkipped('Not supported on legacy Promise v1 API');
  106. }
  107. $connector = new Connector(array());
  108. while (gc_collect_cycles()) {
  109. // collect all garbage cycles
  110. }
  111. $promise = $connector->connect('8.8.8.8:80');
  112. $promise->cancel();
  113. unset($promise);
  114. $this->assertEquals(0, gc_collect_cycles());
  115. }
  116. public function testWaitingForRejectedConnectionShouldNotCreateAnyGarbageReferences()
  117. {
  118. if (class_exists('React\Promise\When')) {
  119. $this->markTestSkipped('Not supported on legacy Promise v1 API');
  120. }
  121. // let loop tick for reactphp/async v4 to clean up any remaining stream resources
  122. // @link https://github.com/reactphp/async/pull/65 reported upstream // TODO remove me once merged
  123. if (function_exists('React\Async\async')) {
  124. \React\Async\await(\React\Promise\Timer\sleep(0));
  125. Loop::run();
  126. }
  127. $connector = new Connector(array('timeout' => false));
  128. while (gc_collect_cycles()) {
  129. // collect all garbage cycles
  130. }
  131. $wait = true;
  132. $promise = $connector->connect('127.0.0.1:1')->then(
  133. null,
  134. function ($e) use (&$wait) {
  135. $wait = false;
  136. }
  137. );
  138. // run loop for short period to ensure we detect connection refused error
  139. \React\Async\await(\React\Promise\Timer\sleep(0.01));
  140. if ($wait) {
  141. \React\Async\await(\React\Promise\Timer\sleep(0.2));
  142. if ($wait) {
  143. \React\Async\await(\React\Promise\Timer\sleep(2.0));
  144. if ($wait) {
  145. $this->fail('Connection attempt did not fail');
  146. }
  147. }
  148. }
  149. unset($promise);
  150. $this->assertEquals(0, gc_collect_cycles());
  151. }
  152. public function testWaitingForConnectionTimeoutDuringDnsLookupShouldNotCreateAnyGarbageReferences()
  153. {
  154. if (class_exists('React\Promise\When')) {
  155. $this->markTestSkipped('Not supported on legacy Promise v1 API');
  156. }
  157. $connector = new Connector(array('timeout' => 0.001));
  158. while (gc_collect_cycles()) {
  159. // collect all garbage cycles
  160. }
  161. $wait = true;
  162. $promise = $connector->connect('google.com:80')->then(
  163. null,
  164. function ($e) use (&$wait) {
  165. $wait = false;
  166. }
  167. );
  168. // run loop for short period to ensure we detect a connection timeout error
  169. \React\Async\await(\React\Promise\Timer\sleep(0.01));
  170. if ($wait) {
  171. \React\Async\await(\React\Promise\Timer\sleep(0.2));
  172. if ($wait) {
  173. $this->fail('Connection attempt did not fail');
  174. }
  175. }
  176. unset($promise);
  177. $this->assertEquals(0, gc_collect_cycles());
  178. }
  179. public function testWaitingForConnectionTimeoutDuringTcpConnectionShouldNotCreateAnyGarbageReferences()
  180. {
  181. if (class_exists('React\Promise\When')) {
  182. $this->markTestSkipped('Not supported on legacy Promise v1 API');
  183. }
  184. $connector = new Connector(array('timeout' => 0.000001));
  185. while (gc_collect_cycles()) {
  186. // collect all garbage cycles
  187. }
  188. $wait = true;
  189. $promise = $connector->connect('8.8.8.8:53')->then(
  190. null,
  191. function ($e) use (&$wait) {
  192. $wait = false;
  193. }
  194. );
  195. // run loop for short period to ensure we detect a connection timeout error
  196. \React\Async\await(\React\Promise\Timer\sleep(0.01));
  197. if ($wait) {
  198. \React\Async\await(\React\Promise\Timer\sleep(0.2));
  199. if ($wait) {
  200. $this->fail('Connection attempt did not fail');
  201. }
  202. }
  203. unset($promise);
  204. $this->assertEquals(0, gc_collect_cycles());
  205. }
  206. public function testWaitingForInvalidDnsConnectionShouldNotCreateAnyGarbageReferences()
  207. {
  208. if (class_exists('React\Promise\When')) {
  209. $this->markTestSkipped('Not supported on legacy Promise v1 API');
  210. }
  211. $connector = new Connector(array('timeout' => false));
  212. while (gc_collect_cycles()) {
  213. // collect all garbage cycles
  214. }
  215. $wait = true;
  216. $promise = $connector->connect('example.invalid:80')->then(
  217. null,
  218. function ($e) use (&$wait) {
  219. $wait = false;
  220. }
  221. );
  222. // run loop for short period to ensure we detect a DNS error
  223. \React\Async\await(\React\Promise\Timer\sleep(0.01));
  224. if ($wait) {
  225. \React\Async\await(\React\Promise\Timer\sleep(0.2));
  226. if ($wait) {
  227. \React\Async\await(\React\Promise\Timer\sleep(2.0));
  228. if ($wait) {
  229. $this->fail('Connection attempt did not fail');
  230. }
  231. }
  232. }
  233. unset($promise);
  234. $this->assertEquals(0, gc_collect_cycles());
  235. }
  236. /**
  237. * @requires PHP 7
  238. */
  239. public function testWaitingForInvalidTlsConnectionShouldNotCreateAnyGarbageReferences()
  240. {
  241. if (class_exists('React\Promise\When')) {
  242. $this->markTestSkipped('Not supported on legacy Promise v1 API');
  243. }
  244. $connector = new Connector(array(
  245. 'tls' => array(
  246. 'verify_peer' => true
  247. )
  248. ));
  249. while (gc_collect_cycles()) {
  250. // collect all garbage cycles
  251. }
  252. $wait = true;
  253. $promise = $connector->connect('tls://self-signed.badssl.com:443')->then(
  254. null,
  255. function ($e) use (&$wait) {
  256. $wait = false;
  257. }
  258. );
  259. // run loop for short period to ensure we detect a TLS error
  260. \React\Async\await(\React\Promise\Timer\sleep(0.01));
  261. if ($wait) {
  262. \React\Async\await(\React\Promise\Timer\sleep(0.4));
  263. if ($wait) {
  264. \React\Async\await(\React\Promise\Timer\sleep(self::TIMEOUT - 0.5));
  265. if ($wait) {
  266. $this->fail('Connection attempt did not fail');
  267. }
  268. }
  269. }
  270. unset($promise);
  271. $this->assertEquals(0, gc_collect_cycles());
  272. }
  273. public function testWaitingForSuccessfullyClosedConnectionShouldNotCreateAnyGarbageReferences()
  274. {
  275. if (class_exists('React\Promise\When')) {
  276. $this->markTestSkipped('Not supported on legacy Promise v1 API');
  277. }
  278. $connector = new Connector(array('timeout' => false));
  279. while (gc_collect_cycles()) {
  280. // collect all garbage cycles
  281. }
  282. $promise = $connector->connect('google.com:80')->then(
  283. function ($conn) {
  284. $conn->close();
  285. }
  286. );
  287. \React\Async\await(\React\Promise\Timer\timeout($promise, self::TIMEOUT));
  288. unset($promise);
  289. $this->assertEquals(0, gc_collect_cycles());
  290. }
  291. public function testConnectingFailsIfTimeoutIsTooSmall()
  292. {
  293. $connector = new Connector(array(
  294. 'timeout' => 0.001
  295. ));
  296. $this->setExpectedException('RuntimeException');
  297. \React\Async\await(\React\Promise\Timer\timeout($connector->connect('google.com:80'), self::TIMEOUT));
  298. }
  299. public function testSelfSignedRejectsIfVerificationIsEnabled()
  300. {
  301. if (defined('HHVM_VERSION')) {
  302. $this->markTestSkipped('Not supported on legacy HHVM');
  303. }
  304. $connector = new Connector(array(
  305. 'tls' => array(
  306. 'verify_peer' => true
  307. )
  308. ));
  309. $this->setExpectedException('RuntimeException');
  310. \React\Async\await(\React\Promise\Timer\timeout($connector->connect('tls://self-signed.badssl.com:443'), self::TIMEOUT));
  311. }
  312. public function testSelfSignedResolvesIfVerificationIsDisabled()
  313. {
  314. if (defined('HHVM_VERSION')) {
  315. $this->markTestSkipped('Not supported on legacy HHVM');
  316. }
  317. $connector = new Connector(array(
  318. 'tls' => array(
  319. 'verify_peer' => false
  320. )
  321. ));
  322. $conn = \React\Async\await(\React\Promise\Timer\timeout($connector->connect('tls://self-signed.badssl.com:443'), self::TIMEOUT));
  323. assert($conn instanceof ConnectionInterface);
  324. $conn->close();
  325. // if we reach this, then everything is good
  326. $this->assertNull(null);
  327. }
  328. }