StreamHandlerTest.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797
  1. <?php
  2. namespace GuzzleHttp\Test\Handler;
  3. use GuzzleHttp\Exception\ConnectException;
  4. use GuzzleHttp\Exception\RequestException;
  5. use GuzzleHttp\Handler\StreamHandler;
  6. use GuzzleHttp\Psr7;
  7. use GuzzleHttp\Psr7\FnStream;
  8. use GuzzleHttp\Psr7\Request;
  9. use GuzzleHttp\Psr7\Response;
  10. use GuzzleHttp\RequestOptions;
  11. use GuzzleHttp\Tests\Server;
  12. use GuzzleHttp\TransferStats;
  13. use GuzzleHttp\Utils;
  14. use PHPUnit\Framework\TestCase;
  15. use Psr\Http\Message\ResponseInterface;
  16. /**
  17. * @covers \GuzzleHttp\Handler\StreamHandler
  18. */
  19. class StreamHandlerTest extends TestCase
  20. {
  21. private function queueRes()
  22. {
  23. Server::flush();
  24. Server::enqueue([
  25. new Response(200, [
  26. 'Foo' => 'Bar',
  27. 'Content-Length' => 8,
  28. ], 'hi there'),
  29. ]);
  30. }
  31. public function testReturnsResponseForSuccessfulRequest()
  32. {
  33. $this->queueRes();
  34. $handler = new StreamHandler();
  35. $response = $handler(
  36. new Request('GET', Server::$url, ['Foo' => 'Bar']),
  37. []
  38. )->wait();
  39. self::assertSame(200, $response->getStatusCode());
  40. self::assertSame('OK', $response->getReasonPhrase());
  41. self::assertSame('Bar', $response->getHeaderLine('Foo'));
  42. self::assertSame('8', $response->getHeaderLine('Content-Length'));
  43. self::assertSame('hi there', (string) $response->getBody());
  44. $sent = Server::received()[0];
  45. self::assertSame('GET', $sent->getMethod());
  46. self::assertSame('/', $sent->getUri()->getPath());
  47. self::assertSame('127.0.0.1:8126', $sent->getHeaderLine('Host'));
  48. self::assertSame('Bar', $sent->getHeaderLine('foo'));
  49. }
  50. public function testAddsErrorToResponse()
  51. {
  52. $handler = new StreamHandler();
  53. $this->expectException(ConnectException::class);
  54. $handler(
  55. new Request('GET', 'http://localhost:123'),
  56. ['timeout' => 0.01]
  57. )->wait();
  58. }
  59. public function testStreamAttributeKeepsStreamOpen()
  60. {
  61. $this->queueRes();
  62. $handler = new StreamHandler();
  63. $request = new Request(
  64. 'PUT',
  65. Server::$url.'foo?baz=bar',
  66. ['Foo' => 'Bar'],
  67. 'test'
  68. );
  69. $response = $handler($request, ['stream' => true])->wait();
  70. self::assertSame(200, $response->getStatusCode());
  71. self::assertSame('OK', $response->getReasonPhrase());
  72. self::assertSame('8', $response->getHeaderLine('Content-Length'));
  73. $body = $response->getBody();
  74. $stream = $body->detach();
  75. self::assertIsResource($stream);
  76. self::assertSame('http', \stream_get_meta_data($stream)['wrapper_type']);
  77. self::assertSame('hi there', \stream_get_contents($stream));
  78. \fclose($stream);
  79. $sent = Server::received()[0];
  80. self::assertSame('PUT', $sent->getMethod());
  81. self::assertSame('http://127.0.0.1:8126/foo?baz=bar', (string) $sent->getUri());
  82. self::assertSame('Bar', $sent->getHeaderLine('Foo'));
  83. self::assertSame('test', (string) $sent->getBody());
  84. }
  85. public function testDrainsResponseIntoTempStream()
  86. {
  87. $this->queueRes();
  88. $handler = new StreamHandler();
  89. $request = new Request('GET', Server::$url);
  90. $response = $handler($request, [])->wait();
  91. $body = $response->getBody();
  92. $stream = $body->detach();
  93. self::assertSame('php://temp', \stream_get_meta_data($stream)['uri']);
  94. self::assertSame('hi', \fread($stream, 2));
  95. \fclose($stream);
  96. }
  97. public function testDrainsResponseIntoSaveToBody()
  98. {
  99. $r = \fopen('php://temp', 'r+');
  100. $this->queueRes();
  101. $handler = new StreamHandler();
  102. $request = new Request('GET', Server::$url);
  103. $response = $handler($request, ['sink' => $r])->wait();
  104. $body = $response->getBody()->detach();
  105. self::assertSame('php://temp', \stream_get_meta_data($body)['uri']);
  106. self::assertSame('hi', \fread($body, 2));
  107. self::assertSame(' there', \stream_get_contents($r));
  108. \fclose($r);
  109. }
  110. public function testDrainsResponseIntoSaveToBodyAtPath()
  111. {
  112. $tmpfname = \tempnam(\sys_get_temp_dir(), 'save_to_path');
  113. $this->queueRes();
  114. $handler = new StreamHandler();
  115. $request = new Request('GET', Server::$url);
  116. $response = $handler($request, ['sink' => $tmpfname])->wait();
  117. $body = $response->getBody();
  118. self::assertSame($tmpfname, $body->getMetadata('uri'));
  119. self::assertSame('hi', $body->read(2));
  120. $body->close();
  121. \unlink($tmpfname);
  122. }
  123. public function testDrainsResponseIntoSaveToBodyAtNonExistentPath()
  124. {
  125. $tmpfname = \tempnam(\sys_get_temp_dir(), 'save_to_path');
  126. \unlink($tmpfname);
  127. $this->queueRes();
  128. $handler = new StreamHandler();
  129. $request = new Request('GET', Server::$url);
  130. $response = $handler($request, ['sink' => $tmpfname])->wait();
  131. $body = $response->getBody();
  132. self::assertSame($tmpfname, $body->getMetadata('uri'));
  133. self::assertSame('hi', $body->read(2));
  134. $body->close();
  135. \unlink($tmpfname);
  136. }
  137. public function testDrainsResponseAndReadsOnlyContentLengthBytes()
  138. {
  139. Server::flush();
  140. Server::enqueue([
  141. new Response(200, [
  142. 'Foo' => 'Bar',
  143. 'Content-Length' => 8,
  144. ], 'hi there... This has way too much data!'),
  145. ]);
  146. $handler = new StreamHandler();
  147. $request = new Request('GET', Server::$url);
  148. $response = $handler($request, [])->wait();
  149. $body = $response->getBody();
  150. $stream = $body->detach();
  151. self::assertSame('hi there', \stream_get_contents($stream));
  152. \fclose($stream);
  153. }
  154. public function testDoesNotDrainWhenHeadRequest()
  155. {
  156. Server::flush();
  157. // Say the content-length is 8, but return no response.
  158. Server::enqueue([
  159. new Response(200, [
  160. 'Foo' => 'Bar',
  161. 'Content-Length' => 8,
  162. ], ''),
  163. ]);
  164. $handler = new StreamHandler();
  165. $request = new Request('HEAD', Server::$url);
  166. $response = $handler($request, [])->wait();
  167. $body = $response->getBody();
  168. $stream = $body->detach();
  169. self::assertSame('', \stream_get_contents($stream));
  170. \fclose($stream);
  171. }
  172. public function testAutomaticallyDecompressGzip()
  173. {
  174. Server::flush();
  175. $content = \gzencode('test');
  176. Server::enqueue([
  177. new Response(200, [
  178. 'Content-Encoding' => 'gzip',
  179. 'Content-Length' => \strlen($content),
  180. ], $content),
  181. ]);
  182. $handler = new StreamHandler();
  183. $request = new Request('GET', Server::$url);
  184. $response = $handler($request, ['decode_content' => true])->wait();
  185. self::assertSame('test', (string) $response->getBody());
  186. self::assertFalse($response->hasHeader('content-encoding'));
  187. self::assertTrue(!$response->hasHeader('content-length') || $response->getHeaderLine('content-length') == $response->getBody()->getSize());
  188. }
  189. public function testAutomaticallyDecompressGzipHead()
  190. {
  191. Server::flush();
  192. $content = \gzencode('test');
  193. Server::enqueue([
  194. new Response(200, [
  195. 'Content-Encoding' => 'gzip',
  196. 'Content-Length' => \strlen($content),
  197. ], $content),
  198. ]);
  199. $handler = new StreamHandler();
  200. $request = new Request('HEAD', Server::$url);
  201. $response = $handler($request, ['decode_content' => true])->wait();
  202. // Verify that the content-length matches the encoded size.
  203. self::assertTrue(!$response->hasHeader('content-length') || $response->getHeaderLine('content-length') == \strlen($content));
  204. }
  205. public function testReportsOriginalSizeAndContentEncodingAfterDecoding()
  206. {
  207. Server::flush();
  208. $content = \gzencode('test');
  209. Server::enqueue([
  210. new Response(200, [
  211. 'Content-Encoding' => 'gzip',
  212. 'Content-Length' => \strlen($content),
  213. ], $content),
  214. ]);
  215. $handler = new StreamHandler();
  216. $request = new Request('GET', Server::$url);
  217. $response = $handler($request, ['decode_content' => true])->wait();
  218. self::assertSame(
  219. 'gzip',
  220. $response->getHeaderLine('x-encoded-content-encoding')
  221. );
  222. self::assertSame(
  223. \strlen($content),
  224. (int) $response->getHeaderLine('x-encoded-content-length')
  225. );
  226. }
  227. public function testDoesNotForceGzipDecode()
  228. {
  229. Server::flush();
  230. $content = \gzencode('test');
  231. Server::enqueue([
  232. new Response(200, [
  233. 'Content-Encoding' => 'gzip',
  234. 'Content-Length' => \strlen($content),
  235. ], $content),
  236. ]);
  237. $handler = new StreamHandler();
  238. $request = new Request('GET', Server::$url);
  239. $response = $handler($request, ['decode_content' => false])->wait();
  240. self::assertSame($content, (string) $response->getBody());
  241. self::assertSame('gzip', $response->getHeaderLine('content-encoding'));
  242. self::assertEquals(\strlen($content), $response->getHeaderLine('content-length'));
  243. }
  244. public function testProtocolVersion()
  245. {
  246. $this->queueRes();
  247. $handler = new StreamHandler();
  248. $request = new Request('GET', Server::$url, [], null, '1.0');
  249. $handler($request, []);
  250. self::assertSame('1.0', Server::received()[0]->getProtocolVersion());
  251. }
  252. protected function getSendResult(array $opts)
  253. {
  254. $this->queueRes();
  255. $handler = new StreamHandler();
  256. $opts['stream'] = true;
  257. $request = new Request('GET', Server::$url);
  258. return $handler($request, $opts)->wait();
  259. }
  260. public function testAddsProxy()
  261. {
  262. $this->expectException(ConnectException::class);
  263. $this->expectExceptionMessage('Connection refused');
  264. $this->getSendResult(['proxy' => '127.0.0.1:8125']);
  265. }
  266. public function testAddsProxyByProtocol()
  267. {
  268. $url = Server::$url;
  269. $res = $this->getSendResult(['proxy' => ['http' => $url]]);
  270. $opts = \stream_context_get_options($res->getBody()->detach());
  271. foreach ([\PHP_URL_HOST, \PHP_URL_PORT] as $part) {
  272. self::assertSame(parse_url($url, $part), parse_url($opts['http']['proxy'], $part));
  273. }
  274. }
  275. public function testAddsProxyButHonorsNoProxy()
  276. {
  277. $url = Server::$url;
  278. $res = $this->getSendResult(['proxy' => [
  279. 'http' => $url,
  280. 'no' => ['*'],
  281. ]]);
  282. $opts = \stream_context_get_options($res->getBody()->detach());
  283. self::assertArrayNotHasKey('proxy', $opts['http']);
  284. }
  285. public function testUsesProxy()
  286. {
  287. $this->queueRes();
  288. $handler = new StreamHandler();
  289. $request = new Request('GET', 'http://www.example.com', [], null, '1.0');
  290. $response = $handler($request, [
  291. 'proxy' => Server::$url,
  292. ])->wait();
  293. self::assertSame(200, $response->getStatusCode());
  294. self::assertSame('OK', $response->getReasonPhrase());
  295. self::assertSame('Bar', $response->getHeaderLine('Foo'));
  296. self::assertSame('8', $response->getHeaderLine('Content-Length'));
  297. self::assertSame('hi there', (string) $response->getBody());
  298. }
  299. public function testAddsTimeout()
  300. {
  301. $res = $this->getSendResult(['stream' => true, 'timeout' => 200]);
  302. $opts = \stream_context_get_options($res->getBody()->detach());
  303. self::assertEquals(200, $opts['http']['timeout']);
  304. }
  305. public function testVerifiesVerifyIsValidIfPath()
  306. {
  307. $this->expectException(RequestException::class);
  308. $this->expectExceptionMessage('SSL CA bundle not found: /does/not/exist');
  309. $this->getSendResult(['verify' => '/does/not/exist']);
  310. }
  311. public function testVerifyCanBeDisabled()
  312. {
  313. $handler = $this->getSendResult(['verify' => false]);
  314. self::assertInstanceOf(Response::class, $handler);
  315. }
  316. public function testVerifiesCertIfValidPath()
  317. {
  318. $this->expectException(RequestException::class);
  319. $this->expectExceptionMessage('SSL certificate not found: /does/not/exist');
  320. $this->getSendResult(['cert' => '/does/not/exist']);
  321. }
  322. public function testVerifyCanBeSetToPath()
  323. {
  324. $path = Utils::defaultCaBundle();
  325. $res = $this->getSendResult(['verify' => $path]);
  326. $opts = \stream_context_get_options($res->getBody()->detach());
  327. self::assertTrue($opts['ssl']['verify_peer']);
  328. self::assertTrue($opts['ssl']['verify_peer_name']);
  329. self::assertSame($path, $opts['ssl']['cafile']);
  330. self::assertFileExists($opts['ssl']['cafile']);
  331. }
  332. public function testUsesSystemDefaultBundle()
  333. {
  334. $res = $this->getSendResult(['verify' => true]);
  335. $opts = \stream_context_get_options($res->getBody()->detach());
  336. self::assertArrayNotHasKey('cafile', $opts['ssl']);
  337. }
  338. public function testEnsuresVerifyOptionIsValid()
  339. {
  340. $this->expectException(\InvalidArgumentException::class);
  341. $this->expectExceptionMessage('Invalid verify request option');
  342. $this->getSendResult(['verify' => 10]);
  343. }
  344. public function testEnsuresCryptoMethodOptionIsValid()
  345. {
  346. $this->expectException(\InvalidArgumentException::class);
  347. $this->expectExceptionMessage('Invalid crypto_method request option: unknown version provided');
  348. $this->getSendResult(['crypto_method' => 123]);
  349. }
  350. public function testSetsCryptoMethodTls10()
  351. {
  352. $res = $this->getSendResult(['crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT]);
  353. $opts = \stream_context_get_options($res->getBody()->detach());
  354. self::assertSame(\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT, $opts['http']['crypto_method']);
  355. }
  356. public function testSetsCryptoMethodTls11()
  357. {
  358. $res = $this->getSendResult(['crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT]);
  359. $opts = \stream_context_get_options($res->getBody()->detach());
  360. self::assertSame(\STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT, $opts['http']['crypto_method']);
  361. }
  362. public function testSetsCryptoMethodTls12()
  363. {
  364. $res = $this->getSendResult(['crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT]);
  365. $opts = \stream_context_get_options($res->getBody()->detach());
  366. self::assertSame(\STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT, $opts['http']['crypto_method']);
  367. }
  368. /**
  369. * @requires PHP >=7.4
  370. */
  371. public function testSetsCryptoMethodTls13()
  372. {
  373. $res = $this->getSendResult(['crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT]);
  374. $opts = \stream_context_get_options($res->getBody()->detach());
  375. self::assertSame(\STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT, $opts['http']['crypto_method']);
  376. }
  377. public function testCanSetPasswordWhenSettingCert()
  378. {
  379. $path = __FILE__;
  380. $res = $this->getSendResult(['cert' => [$path, 'foo']]);
  381. $opts = \stream_context_get_options($res->getBody()->detach());
  382. self::assertSame($path, $opts['ssl']['local_cert']);
  383. self::assertSame('foo', $opts['ssl']['passphrase']);
  384. }
  385. public function testDebugAttributeWritesToStream()
  386. {
  387. $this->queueRes();
  388. $f = \fopen('php://temp', 'w+');
  389. $this->getSendResult(['debug' => $f]);
  390. \fseek($f, 0);
  391. $contents = \stream_get_contents($f);
  392. self::assertStringContainsString('<GET http://127.0.0.1:8126/> [CONNECT]', $contents);
  393. self::assertStringContainsString('<GET http://127.0.0.1:8126/> [FILE_SIZE_IS]', $contents);
  394. self::assertStringContainsString('<GET http://127.0.0.1:8126/> [PROGRESS]', $contents);
  395. }
  396. public function testDebugAttributeWritesStreamInfoToBuffer()
  397. {
  398. $called = false;
  399. $this->queueRes();
  400. $buffer = \fopen('php://temp', 'r+');
  401. $this->getSendResult([
  402. 'progress' => static function () use (&$called) {
  403. $called = true;
  404. },
  405. 'debug' => $buffer,
  406. ]);
  407. \fseek($buffer, 0);
  408. $contents = \stream_get_contents($buffer);
  409. self::assertStringContainsString('<GET http://127.0.0.1:8126/> [CONNECT]', $contents);
  410. self::assertStringContainsString('<GET http://127.0.0.1:8126/> [FILE_SIZE_IS] message: "Content-Length: 8"', $contents);
  411. self::assertStringContainsString('<GET http://127.0.0.1:8126/> [PROGRESS] bytes_max: "8"', $contents);
  412. self::assertTrue($called);
  413. }
  414. public function testEmitsProgressInformation()
  415. {
  416. $called = [];
  417. $this->queueRes();
  418. $this->getSendResult([
  419. 'progress' => static function (...$args) use (&$called) {
  420. $called[] = $args;
  421. },
  422. ]);
  423. self::assertNotEmpty($called);
  424. self::assertEquals(8, $called[0][0]);
  425. self::assertEquals(0, $called[0][1]);
  426. }
  427. public function testEmitsProgressInformationAndDebugInformation()
  428. {
  429. $called = [];
  430. $this->queueRes();
  431. $buffer = \fopen('php://memory', 'w+');
  432. $this->getSendResult([
  433. 'debug' => $buffer,
  434. 'progress' => static function (...$args) use (&$called) {
  435. $called[] = $args;
  436. },
  437. ]);
  438. self::assertNotEmpty($called);
  439. self::assertEquals(8, $called[0][0]);
  440. self::assertEquals(0, $called[0][1]);
  441. \rewind($buffer);
  442. self::assertNotEmpty(\stream_get_contents($buffer));
  443. \fclose($buffer);
  444. }
  445. public function testPerformsShallowMergeOfCustomContextOptions()
  446. {
  447. $res = $this->getSendResult([
  448. 'stream_context' => [
  449. 'http' => [
  450. 'request_fulluri' => true,
  451. 'method' => 'HEAD',
  452. ],
  453. 'socket' => [
  454. 'bindto' => '127.0.0.1:0',
  455. ],
  456. 'ssl' => [
  457. 'verify_peer' => false,
  458. ],
  459. ],
  460. ]);
  461. $opts = \stream_context_get_options($res->getBody()->detach());
  462. self::assertSame('HEAD', $opts['http']['method']);
  463. self::assertTrue($opts['http']['request_fulluri']);
  464. self::assertSame('127.0.0.1:0', $opts['socket']['bindto']);
  465. self::assertFalse($opts['ssl']['verify_peer']);
  466. }
  467. public function testEnsuresThatStreamContextIsAnArray()
  468. {
  469. $this->expectException(\InvalidArgumentException::class);
  470. $this->expectExceptionMessage('stream_context must be an array');
  471. $this->getSendResult(['stream_context' => 'foo']);
  472. }
  473. public function testDoesNotAddContentTypeByDefault()
  474. {
  475. $this->queueRes();
  476. $handler = new StreamHandler();
  477. $request = new Request('PUT', Server::$url, ['Content-Length' => 3], 'foo');
  478. $handler($request, []);
  479. $req = Server::received()[0];
  480. self::assertEquals('', $req->getHeaderLine('Content-Type'));
  481. self::assertEquals(3, $req->getHeaderLine('Content-Length'));
  482. }
  483. public function testAddsContentLengthByDefault()
  484. {
  485. $this->queueRes();
  486. $handler = new StreamHandler();
  487. $request = new Request('PUT', Server::$url, [], 'foo');
  488. $handler($request, []);
  489. $req = Server::received()[0];
  490. self::assertEquals(3, $req->getHeaderLine('Content-Length'));
  491. }
  492. public function testAddsContentLengthEvenWhenEmpty()
  493. {
  494. $this->queueRes();
  495. $handler = new StreamHandler();
  496. $request = new Request('PUT', Server::$url, [], '');
  497. $handler($request, []);
  498. $req = Server::received()[0];
  499. self::assertEquals(0, $req->getHeaderLine('Content-Length'));
  500. }
  501. public function testSupports100Continue()
  502. {
  503. Server::flush();
  504. $response = new Response(200, ['Test' => 'Hello', 'Content-Length' => '4'], 'test');
  505. Server::enqueue([$response]);
  506. $request = new Request('PUT', Server::$url, ['Expect' => '100-Continue'], 'test');
  507. $handler = new StreamHandler();
  508. $response = $handler($request, [])->wait();
  509. self::assertSame(200, $response->getStatusCode());
  510. self::assertSame('Hello', $response->getHeaderLine('Test'));
  511. self::assertSame('4', $response->getHeaderLine('Content-Length'));
  512. self::assertSame('test', (string) $response->getBody());
  513. }
  514. public function testDoesSleep()
  515. {
  516. $response = new response(200);
  517. Server::enqueue([$response]);
  518. $a = new StreamHandler();
  519. $request = new Request('GET', Server::$url);
  520. $s = Utils::currentTime();
  521. $a($request, ['delay' => 0.1])->wait();
  522. self::assertGreaterThan(0.0001, Utils::currentTime() - $s);
  523. }
  524. public function testEnsuresOnHeadersIsCallable()
  525. {
  526. $req = new Request('GET', Server::$url);
  527. $handler = new StreamHandler();
  528. $this->expectException(\InvalidArgumentException::class);
  529. $handler($req, ['on_headers' => 'error!']);
  530. }
  531. public function testRejectsPromiseWhenOnHeadersFails()
  532. {
  533. Server::flush();
  534. Server::enqueue([
  535. new Response(200, ['X-Foo' => 'bar'], 'abc 123'),
  536. ]);
  537. $req = new Request('GET', Server::$url);
  538. $handler = new StreamHandler();
  539. $promise = $handler($req, [
  540. 'on_headers' => static function () {
  541. throw new \Exception('test');
  542. },
  543. ]);
  544. $this->expectException(RequestException::class);
  545. $this->expectExceptionMessage('An error was encountered during the on_headers event');
  546. $promise->wait();
  547. }
  548. public function testSuccessfullyCallsOnHeadersBeforeWritingToSink()
  549. {
  550. Server::flush();
  551. Server::enqueue([
  552. new Response(200, ['X-Foo' => 'bar'], 'abc 123'),
  553. ]);
  554. $req = new Request('GET', Server::$url);
  555. $got = null;
  556. $stream = Psr7\Utils::streamFor();
  557. $stream = FnStream::decorate($stream, [
  558. 'write' => static function ($data) use ($stream, &$got) {
  559. self::assertNotNull($got);
  560. return $stream->write($data);
  561. },
  562. ]);
  563. $handler = new StreamHandler();
  564. $promise = $handler($req, [
  565. 'sink' => $stream,
  566. 'on_headers' => static function (ResponseInterface $res) use (&$got) {
  567. $got = $res;
  568. self::assertSame('bar', $res->getHeaderLine('X-Foo'));
  569. },
  570. ]);
  571. $response = $promise->wait();
  572. self::assertSame(200, $response->getStatusCode());
  573. self::assertSame('bar', $response->getHeaderLine('X-Foo'));
  574. self::assertSame('abc 123', (string) $response->getBody());
  575. }
  576. public function testInvokesOnStatsOnSuccess()
  577. {
  578. Server::flush();
  579. Server::enqueue([new Psr7\Response(200)]);
  580. $req = new Psr7\Request('GET', Server::$url);
  581. $gotStats = null;
  582. $handler = new StreamHandler();
  583. $promise = $handler($req, [
  584. 'on_stats' => static function (TransferStats $stats) use (&$gotStats) {
  585. $gotStats = $stats;
  586. },
  587. ]);
  588. $response = $promise->wait();
  589. self::assertSame(200, $response->getStatusCode());
  590. self::assertSame(200, $gotStats->getResponse()->getStatusCode());
  591. self::assertSame(
  592. Server::$url,
  593. (string) $gotStats->getEffectiveUri()
  594. );
  595. self::assertSame(
  596. Server::$url,
  597. (string) $gotStats->getRequest()->getUri()
  598. );
  599. self::assertGreaterThan(0, $gotStats->getTransferTime());
  600. }
  601. public function testInvokesOnStatsOnError()
  602. {
  603. $req = new Psr7\Request('GET', 'http://127.0.0.1:123');
  604. $gotStats = null;
  605. $handler = new StreamHandler();
  606. $promise = $handler($req, [
  607. 'connect_timeout' => 0.001,
  608. 'timeout' => 0.001,
  609. 'on_stats' => static function (TransferStats $stats) use (&$gotStats) {
  610. $gotStats = $stats;
  611. },
  612. ]);
  613. $promise->wait(false);
  614. self::assertFalse($gotStats->hasResponse());
  615. self::assertSame(
  616. 'http://127.0.0.1:123',
  617. (string) $gotStats->getEffectiveUri()
  618. );
  619. self::assertSame(
  620. 'http://127.0.0.1:123',
  621. (string) $gotStats->getRequest()->getUri()
  622. );
  623. self::assertIsFloat($gotStats->getTransferTime());
  624. self::assertInstanceOf(
  625. ConnectException::class,
  626. $gotStats->getHandlerErrorData()
  627. );
  628. }
  629. public function testStreamIgnoresZeroTimeout()
  630. {
  631. Server::flush();
  632. Server::enqueue([new Psr7\Response(200)]);
  633. $req = new Psr7\Request('GET', Server::$url);
  634. $gotStats = null;
  635. $handler = new StreamHandler();
  636. $promise = $handler($req, [
  637. 'connect_timeout' => 10,
  638. 'timeout' => 0,
  639. ]);
  640. $response = $promise->wait();
  641. self::assertSame(200, $response->getStatusCode());
  642. }
  643. public function testDrainsResponseAndReadsAllContentWhenContentLengthIsZero()
  644. {
  645. Server::flush();
  646. Server::enqueue([
  647. new Response(200, [
  648. 'Foo' => 'Bar',
  649. 'Content-Length' => '0',
  650. ], 'hi there... This has a lot of data!'),
  651. ]);
  652. $handler = new StreamHandler();
  653. $request = new Request('GET', Server::$url);
  654. $response = $handler($request, [])->wait();
  655. $body = $response->getBody();
  656. $stream = $body->detach();
  657. self::assertSame('hi there... This has a lot of data!', \stream_get_contents($stream));
  658. \fclose($stream);
  659. }
  660. public function testHonorsReadTimeout()
  661. {
  662. Server::flush();
  663. $handler = new StreamHandler();
  664. $response = $handler(
  665. new Request('GET', Server::$url.'guzzle-server/read-timeout'),
  666. [
  667. RequestOptions::READ_TIMEOUT => 1,
  668. RequestOptions::STREAM => true,
  669. ]
  670. )->wait();
  671. self::assertSame(200, $response->getStatusCode());
  672. self::assertSame('OK', $response->getReasonPhrase());
  673. $body = $response->getBody()->detach();
  674. $line = \fgets($body);
  675. self::assertSame("sleeping 60 seconds ...\n", $line);
  676. $line = \fgets($body);
  677. self::assertFalse($line);
  678. self::assertTrue(\stream_get_meta_data($body)['timed_out']);
  679. self::assertFalse(\feof($body));
  680. }
  681. public function testHandlesGarbageHttpServerGracefully()
  682. {
  683. $handler = new StreamHandler();
  684. $this->expectException(RequestException::class);
  685. $this->expectExceptionMessage('An error was encountered while creating the response');
  686. $handler(
  687. new Request('GET', Server::$url.'guzzle-server/garbage'),
  688. [
  689. RequestOptions::STREAM => true,
  690. ]
  691. )->wait();
  692. }
  693. public function testHandlesInvalidStatusCodeGracefully()
  694. {
  695. $handler = new StreamHandler();
  696. $this->expectException(RequestException::class);
  697. $this->expectExceptionMessage('An error was encountered while creating the response');
  698. $handler(
  699. new Request('GET', Server::$url.'guzzle-server/bad-status'),
  700. [
  701. RequestOptions::STREAM => true,
  702. ]
  703. )->wait();
  704. }
  705. public function testRejectsNonHttpSchemes()
  706. {
  707. $handler = new StreamHandler();
  708. $this->expectException(RequestException::class);
  709. $this->expectExceptionMessage("The scheme 'file' is not supported.");
  710. $handler(
  711. new Request('GET', 'file:///etc/passwd'),
  712. [
  713. RequestOptions::STREAM => true,
  714. ]
  715. )->wait();
  716. }
  717. }