HttpClientTestCase.php 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Contracts\HttpClient\Test;
  11. use PHPUnit\Framework\TestCase;
  12. use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
  13. use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
  14. use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
  15. use Symfony\Contracts\HttpClient\HttpClientInterface;
  16. /**
  17. * A reference test suite for HttpClientInterface implementations.
  18. *
  19. * @experimental in 1.1
  20. */
  21. abstract class HttpClientTestCase extends TestCase
  22. {
  23. public static function setUpBeforeClass(): void
  24. {
  25. TestHttpServer::start();
  26. }
  27. abstract protected function getHttpClient(string $testCase): HttpClientInterface;
  28. public function testGetRequest()
  29. {
  30. $client = $this->getHttpClient(__FUNCTION__);
  31. $response = $client->request('GET', 'http://localhost:8057', [
  32. 'headers' => ['Foo' => 'baR'],
  33. 'user_data' => $data = new \stdClass(),
  34. ]);
  35. $this->assertSame([], $response->getInfo('response_headers'));
  36. $this->assertSame($data, $response->getInfo()['user_data']);
  37. $this->assertSame(200, $response->getStatusCode());
  38. $info = $response->getInfo();
  39. $this->assertNull($info['error']);
  40. $this->assertSame(0, $info['redirect_count']);
  41. $this->assertSame('HTTP/1.1 200 OK', $info['response_headers'][0]);
  42. $this->assertSame('Host: localhost:8057', $info['response_headers'][1]);
  43. $this->assertSame('http://localhost:8057/', $info['url']);
  44. $headers = $response->getHeaders();
  45. $this->assertSame('localhost:8057', $headers['host'][0]);
  46. $this->assertSame(['application/json'], $headers['content-type']);
  47. $body = json_decode($response->getContent(), true);
  48. $this->assertSame($body, $response->toArray());
  49. $this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']);
  50. $this->assertSame('/', $body['REQUEST_URI']);
  51. $this->assertSame('GET', $body['REQUEST_METHOD']);
  52. $this->assertSame('localhost:8057', $body['HTTP_HOST']);
  53. $this->assertSame('baR', $body['HTTP_FOO']);
  54. $response = $client->request('GET', 'http://localhost:8057/length-broken');
  55. $this->expectException(TransportExceptionInterface::class);
  56. $response->getContent();
  57. }
  58. public function testHeadRequest()
  59. {
  60. $client = $this->getHttpClient(__FUNCTION__);
  61. $response = $client->request('HEAD', 'http://localhost:8057/head', [
  62. 'headers' => ['Foo' => 'baR'],
  63. 'user_data' => $data = new \stdClass(),
  64. 'buffer' => false,
  65. ]);
  66. $this->assertSame([], $response->getInfo('response_headers'));
  67. $this->assertSame(200, $response->getStatusCode());
  68. $info = $response->getInfo();
  69. $this->assertSame('HTTP/1.1 200 OK', $info['response_headers'][0]);
  70. $this->assertSame('Host: localhost:8057', $info['response_headers'][1]);
  71. $headers = $response->getHeaders();
  72. $this->assertSame('localhost:8057', $headers['host'][0]);
  73. $this->assertSame(['application/json'], $headers['content-type']);
  74. $this->assertTrue(0 < $headers['content-length'][0]);
  75. $this->assertSame('', $response->getContent());
  76. }
  77. public function testNonBufferedGetRequest()
  78. {
  79. $client = $this->getHttpClient(__FUNCTION__);
  80. $response = $client->request('GET', 'http://localhost:8057', [
  81. 'buffer' => false,
  82. 'headers' => ['Foo' => 'baR'],
  83. ]);
  84. $body = $response->toArray();
  85. $this->assertSame('baR', $body['HTTP_FOO']);
  86. $this->expectException(TransportExceptionInterface::class);
  87. $response->getContent();
  88. }
  89. public function testBufferSink()
  90. {
  91. $sink = fopen('php://temp', 'w+');
  92. $client = $this->getHttpClient(__FUNCTION__);
  93. $response = $client->request('GET', 'http://localhost:8057', [
  94. 'buffer' => $sink,
  95. 'headers' => ['Foo' => 'baR'],
  96. ]);
  97. $body = $response->toArray();
  98. $this->assertSame('baR', $body['HTTP_FOO']);
  99. rewind($sink);
  100. $sink = stream_get_contents($sink);
  101. $this->assertSame($sink, $response->getContent());
  102. }
  103. public function testConditionalBuffering()
  104. {
  105. $client = $this->getHttpClient(__FUNCTION__);
  106. $response = $client->request('GET', 'http://localhost:8057');
  107. $firstContent = $response->getContent();
  108. $secondContent = $response->getContent();
  109. $this->assertSame($firstContent, $secondContent);
  110. $response = $client->request('GET', 'http://localhost:8057', ['buffer' => function () { return false; }]);
  111. $response->getContent();
  112. $this->expectException(TransportExceptionInterface::class);
  113. $response->getContent();
  114. }
  115. public function testReentrantBufferCallback()
  116. {
  117. $client = $this->getHttpClient(__FUNCTION__);
  118. $response = $client->request('GET', 'http://localhost:8057', ['buffer' => function () use (&$response) {
  119. $response->cancel();
  120. return true;
  121. }]);
  122. $this->assertSame(200, $response->getStatusCode());
  123. $this->expectException(TransportExceptionInterface::class);
  124. $response->getContent();
  125. }
  126. public function testThrowingBufferCallback()
  127. {
  128. $client = $this->getHttpClient(__FUNCTION__);
  129. $response = $client->request('GET', 'http://localhost:8057', ['buffer' => function () {
  130. throw new \Exception('Boo.');
  131. }]);
  132. $this->assertSame(200, $response->getStatusCode());
  133. $this->expectException(TransportExceptionInterface::class);
  134. $this->expectExceptionMessage('Boo');
  135. $response->getContent();
  136. }
  137. public function testUnsupportedOption()
  138. {
  139. $client = $this->getHttpClient(__FUNCTION__);
  140. $this->expectException(\InvalidArgumentException::class);
  141. $client->request('GET', 'http://localhost:8057', [
  142. 'capture_peer_cert' => 1.0,
  143. ]);
  144. }
  145. public function testHttpVersion()
  146. {
  147. $client = $this->getHttpClient(__FUNCTION__);
  148. $response = $client->request('GET', 'http://localhost:8057', [
  149. 'http_version' => 1.0,
  150. ]);
  151. $this->assertSame(200, $response->getStatusCode());
  152. $this->assertSame('HTTP/1.0 200 OK', $response->getInfo('response_headers')[0]);
  153. $body = $response->toArray();
  154. $this->assertSame('HTTP/1.0', $body['SERVER_PROTOCOL']);
  155. $this->assertSame('GET', $body['REQUEST_METHOD']);
  156. $this->assertSame('/', $body['REQUEST_URI']);
  157. }
  158. public function testChunkedEncoding()
  159. {
  160. $client = $this->getHttpClient(__FUNCTION__);
  161. $response = $client->request('GET', 'http://localhost:8057/chunked');
  162. $this->assertSame(['chunked'], $response->getHeaders()['transfer-encoding']);
  163. $this->assertSame('Symfony is awesome!', $response->getContent());
  164. $response = $client->request('GET', 'http://localhost:8057/chunked-broken');
  165. $this->expectException(TransportExceptionInterface::class);
  166. $response->getContent();
  167. }
  168. public function testClientError()
  169. {
  170. $client = $this->getHttpClient(__FUNCTION__);
  171. $response = $client->request('GET', 'http://localhost:8057/404');
  172. $client->stream($response)->valid();
  173. $this->assertSame(404, $response->getInfo('http_code'));
  174. try {
  175. $response->getHeaders();
  176. $this->fail(ClientExceptionInterface::class.' expected');
  177. } catch (ClientExceptionInterface $e) {
  178. }
  179. try {
  180. $response->getContent();
  181. $this->fail(ClientExceptionInterface::class.' expected');
  182. } catch (ClientExceptionInterface $e) {
  183. }
  184. $this->assertSame(404, $response->getStatusCode());
  185. $this->assertSame(['application/json'], $response->getHeaders(false)['content-type']);
  186. $this->assertNotEmpty($response->getContent(false));
  187. $response = $client->request('GET', 'http://localhost:8057/404');
  188. try {
  189. foreach ($client->stream($response) as $chunk) {
  190. $this->assertTrue($chunk->isFirst());
  191. }
  192. $this->fail(ClientExceptionInterface::class.' expected');
  193. } catch (ClientExceptionInterface $e) {
  194. }
  195. }
  196. public function testIgnoreErrors()
  197. {
  198. $client = $this->getHttpClient(__FUNCTION__);
  199. $response = $client->request('GET', 'http://localhost:8057/404');
  200. $this->assertSame(404, $response->getStatusCode());
  201. }
  202. public function testDnsError()
  203. {
  204. $client = $this->getHttpClient(__FUNCTION__);
  205. $response = $client->request('GET', 'http://localhost:8057/301/bad-tld');
  206. try {
  207. $response->getStatusCode();
  208. $this->fail(TransportExceptionInterface::class.' expected');
  209. } catch (TransportExceptionInterface $e) {
  210. $this->addToAssertionCount(1);
  211. }
  212. try {
  213. $response->getStatusCode();
  214. $this->fail(TransportExceptionInterface::class.' still expected');
  215. } catch (TransportExceptionInterface $e) {
  216. $this->addToAssertionCount(1);
  217. }
  218. $response = $client->request('GET', 'http://localhost:8057/301/bad-tld');
  219. try {
  220. foreach ($client->stream($response) as $r => $chunk) {
  221. }
  222. $this->fail(TransportExceptionInterface::class.' expected');
  223. } catch (TransportExceptionInterface $e) {
  224. $this->addToAssertionCount(1);
  225. }
  226. $this->assertSame($response, $r);
  227. $this->assertNotNull($chunk->getError());
  228. $this->expectException(TransportExceptionInterface::class);
  229. foreach ($client->stream($response) as $chunk) {
  230. }
  231. }
  232. public function testInlineAuth()
  233. {
  234. $client = $this->getHttpClient(__FUNCTION__);
  235. $response = $client->request('GET', 'http://foo:bar%3Dbar@localhost:8057');
  236. $body = $response->toArray();
  237. $this->assertSame('foo', $body['PHP_AUTH_USER']);
  238. $this->assertSame('bar=bar', $body['PHP_AUTH_PW']);
  239. }
  240. public function testBadRequestBody()
  241. {
  242. $client = $this->getHttpClient(__FUNCTION__);
  243. $this->expectException(TransportExceptionInterface::class);
  244. $response = $client->request('POST', 'http://localhost:8057/', [
  245. 'body' => function () { yield []; },
  246. ]);
  247. $response->getStatusCode();
  248. }
  249. public function test304()
  250. {
  251. $client = $this->getHttpClient(__FUNCTION__);
  252. $response = $client->request('GET', 'http://localhost:8057/304', [
  253. 'headers' => ['If-Match' => '"abc"'],
  254. 'buffer' => false,
  255. ]);
  256. $this->assertSame(304, $response->getStatusCode());
  257. $this->assertSame('', $response->getContent(false));
  258. }
  259. public function testRedirects()
  260. {
  261. $client = $this->getHttpClient(__FUNCTION__);
  262. $response = $client->request('POST', 'http://localhost:8057/301', [
  263. 'auth_basic' => 'foo:bar',
  264. 'body' => function () {
  265. yield 'foo=bar';
  266. },
  267. ]);
  268. $body = $response->toArray();
  269. $this->assertSame('GET', $body['REQUEST_METHOD']);
  270. $this->assertSame('Basic Zm9vOmJhcg==', $body['HTTP_AUTHORIZATION']);
  271. $this->assertSame('http://localhost:8057/', $response->getInfo('url'));
  272. $this->assertSame(2, $response->getInfo('redirect_count'));
  273. $this->assertNull($response->getInfo('redirect_url'));
  274. $expected = [
  275. 'HTTP/1.1 301 Moved Permanently',
  276. 'Location: http://127.0.0.1:8057/302',
  277. 'Content-Type: application/json',
  278. 'HTTP/1.1 302 Found',
  279. 'Location: http://localhost:8057/',
  280. 'Content-Type: application/json',
  281. 'HTTP/1.1 200 OK',
  282. 'Content-Type: application/json',
  283. ];
  284. $filteredHeaders = array_values(array_filter($response->getInfo('response_headers'), function ($h) {
  285. return \in_array(substr($h, 0, 4), ['HTTP', 'Loca', 'Cont'], true) && 'Content-Encoding: gzip' !== $h;
  286. }));
  287. $this->assertSame($expected, $filteredHeaders);
  288. }
  289. public function testInvalidRedirect()
  290. {
  291. $client = $this->getHttpClient(__FUNCTION__);
  292. $response = $client->request('GET', 'http://localhost:8057/301/invalid');
  293. $this->assertSame(301, $response->getStatusCode());
  294. $this->assertSame(['//?foo=bar'], $response->getHeaders(false)['location']);
  295. $this->assertSame(0, $response->getInfo('redirect_count'));
  296. $this->assertNull($response->getInfo('redirect_url'));
  297. $this->expectException(RedirectionExceptionInterface::class);
  298. $response->getHeaders();
  299. }
  300. public function testRelativeRedirects()
  301. {
  302. $client = $this->getHttpClient(__FUNCTION__);
  303. $response = $client->request('GET', 'http://localhost:8057/302/relative');
  304. $body = $response->toArray();
  305. $this->assertSame('/', $body['REQUEST_URI']);
  306. $this->assertNull($response->getInfo('redirect_url'));
  307. $response = $client->request('GET', 'http://localhost:8057/302/relative', [
  308. 'max_redirects' => 0,
  309. ]);
  310. $this->assertSame(302, $response->getStatusCode());
  311. $this->assertSame('http://localhost:8057/', $response->getInfo('redirect_url'));
  312. }
  313. public function testRedirect307()
  314. {
  315. $client = $this->getHttpClient(__FUNCTION__);
  316. $response = $client->request('POST', 'http://localhost:8057/307', [
  317. 'body' => function () {
  318. yield 'foo=bar';
  319. },
  320. 'max_redirects' => 0,
  321. ]);
  322. $this->assertSame(307, $response->getStatusCode());
  323. $response = $client->request('POST', 'http://localhost:8057/307', [
  324. 'body' => 'foo=bar',
  325. ]);
  326. $body = $response->toArray();
  327. $this->assertSame(['foo' => 'bar', 'REQUEST_METHOD' => 'POST'], $body);
  328. }
  329. public function testMaxRedirects()
  330. {
  331. $client = $this->getHttpClient(__FUNCTION__);
  332. $response = $client->request('GET', 'http://localhost:8057/301', [
  333. 'max_redirects' => 1,
  334. 'auth_basic' => 'foo:bar',
  335. ]);
  336. try {
  337. $response->getHeaders();
  338. $this->fail(RedirectionExceptionInterface::class.' expected');
  339. } catch (RedirectionExceptionInterface $e) {
  340. }
  341. $this->assertSame(302, $response->getStatusCode());
  342. $this->assertSame(1, $response->getInfo('redirect_count'));
  343. $this->assertSame('http://localhost:8057/', $response->getInfo('redirect_url'));
  344. $expected = [
  345. 'HTTP/1.1 301 Moved Permanently',
  346. 'Location: http://127.0.0.1:8057/302',
  347. 'Content-Type: application/json',
  348. 'HTTP/1.1 302 Found',
  349. 'Location: http://localhost:8057/',
  350. 'Content-Type: application/json',
  351. ];
  352. $filteredHeaders = array_values(array_filter($response->getInfo('response_headers'), function ($h) {
  353. return \in_array(substr($h, 0, 4), ['HTTP', 'Loca', 'Cont'], true);
  354. }));
  355. $this->assertSame($expected, $filteredHeaders);
  356. }
  357. public function testStream()
  358. {
  359. $client = $this->getHttpClient(__FUNCTION__);
  360. $response = $client->request('GET', 'http://localhost:8057');
  361. $chunks = $client->stream($response);
  362. $result = [];
  363. foreach ($chunks as $r => $chunk) {
  364. if ($chunk->isTimeout()) {
  365. $result[] = 't';
  366. } elseif ($chunk->isLast()) {
  367. $result[] = 'l';
  368. } elseif ($chunk->isFirst()) {
  369. $result[] = 'f';
  370. }
  371. }
  372. $this->assertSame($response, $r);
  373. $this->assertSame(['f', 'l'], $result);
  374. $chunk = null;
  375. $i = 0;
  376. foreach ($client->stream($response) as $chunk) {
  377. ++$i;
  378. }
  379. $this->assertSame(1, $i);
  380. $this->assertTrue($chunk->isLast());
  381. }
  382. public function testAddToStream()
  383. {
  384. $client = $this->getHttpClient(__FUNCTION__);
  385. $r1 = $client->request('GET', 'http://localhost:8057');
  386. $completed = [];
  387. $pool = [$r1];
  388. while ($pool) {
  389. $chunks = $client->stream($pool);
  390. $pool = [];
  391. foreach ($chunks as $r => $chunk) {
  392. if (!$chunk->isLast()) {
  393. continue;
  394. }
  395. if ($r1 === $r) {
  396. $r2 = $client->request('GET', 'http://localhost:8057');
  397. $pool[] = $r2;
  398. }
  399. $completed[] = $r;
  400. }
  401. }
  402. $this->assertSame([$r1, $r2], $completed);
  403. }
  404. public function testCompleteTypeError()
  405. {
  406. $client = $this->getHttpClient(__FUNCTION__);
  407. $this->expectException(\TypeError::class);
  408. $client->stream(123);
  409. }
  410. public function testOnProgress()
  411. {
  412. $client = $this->getHttpClient(__FUNCTION__);
  413. $response = $client->request('POST', 'http://localhost:8057/post', [
  414. 'headers' => ['Content-Length' => 14],
  415. 'body' => 'foo=0123456789',
  416. 'on_progress' => function (...$state) use (&$steps) { $steps[] = $state; },
  417. ]);
  418. $body = $response->toArray();
  419. $this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $body);
  420. $this->assertSame([0, 0], \array_slice($steps[0], 0, 2));
  421. $lastStep = \array_slice($steps, -1)[0];
  422. $this->assertSame([57, 57], \array_slice($lastStep, 0, 2));
  423. $this->assertSame('http://localhost:8057/post', $steps[0][2]['url']);
  424. }
  425. public function testPostJson()
  426. {
  427. $client = $this->getHttpClient(__FUNCTION__);
  428. $response = $client->request('POST', 'http://localhost:8057/post', [
  429. 'json' => ['foo' => 'bar'],
  430. ]);
  431. $body = $response->toArray();
  432. $this->assertStringContainsString('json', $body['content-type']);
  433. unset($body['content-type']);
  434. $this->assertSame(['foo' => 'bar', 'REQUEST_METHOD' => 'POST'], $body);
  435. }
  436. public function testPostArray()
  437. {
  438. $client = $this->getHttpClient(__FUNCTION__);
  439. $response = $client->request('POST', 'http://localhost:8057/post', [
  440. 'body' => ['foo' => 'bar'],
  441. ]);
  442. $this->assertSame(['foo' => 'bar', 'REQUEST_METHOD' => 'POST'], $response->toArray());
  443. }
  444. public function testPostResource()
  445. {
  446. $client = $this->getHttpClient(__FUNCTION__);
  447. $h = fopen('php://temp', 'w+');
  448. fwrite($h, 'foo=0123456789');
  449. rewind($h);
  450. $response = $client->request('POST', 'http://localhost:8057/post', [
  451. 'body' => $h,
  452. ]);
  453. $body = $response->toArray();
  454. $this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $body);
  455. }
  456. public function testPostCallback()
  457. {
  458. $client = $this->getHttpClient(__FUNCTION__);
  459. $response = $client->request('POST', 'http://localhost:8057/post', [
  460. 'body' => function () {
  461. yield 'foo';
  462. yield '';
  463. yield '=';
  464. yield '0123456789';
  465. },
  466. ]);
  467. $this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $response->toArray());
  468. }
  469. public function testCancel()
  470. {
  471. $client = $this->getHttpClient(__FUNCTION__);
  472. $response = $client->request('GET', 'http://localhost:8057/timeout-header');
  473. $response->cancel();
  474. $this->expectException(TransportExceptionInterface::class);
  475. $response->getHeaders();
  476. }
  477. public function testInfoOnCanceledResponse()
  478. {
  479. $client = $this->getHttpClient(__FUNCTION__);
  480. $response = $client->request('GET', 'http://localhost:8057/timeout-header');
  481. $this->assertFalse($response->getInfo('canceled'));
  482. $response->cancel();
  483. $this->assertTrue($response->getInfo('canceled'));
  484. }
  485. public function testCancelInStream()
  486. {
  487. $client = $this->getHttpClient(__FUNCTION__);
  488. $response = $client->request('GET', 'http://localhost:8057/404');
  489. foreach ($client->stream($response) as $chunk) {
  490. $response->cancel();
  491. }
  492. $this->expectException(TransportExceptionInterface::class);
  493. foreach ($client->stream($response) as $chunk) {
  494. }
  495. }
  496. public function testOnProgressCancel()
  497. {
  498. $client = $this->getHttpClient(__FUNCTION__);
  499. $response = $client->request('GET', 'http://localhost:8057/timeout-body', [
  500. 'on_progress' => function ($dlNow) {
  501. if (0 < $dlNow) {
  502. throw new \Exception('Aborting the request.');
  503. }
  504. },
  505. ]);
  506. try {
  507. foreach ($client->stream([$response]) as $chunk) {
  508. }
  509. $this->fail(ClientExceptionInterface::class.' expected');
  510. } catch (TransportExceptionInterface $e) {
  511. $this->assertSame('Aborting the request.', $e->getPrevious()->getMessage());
  512. }
  513. $this->assertNotNull($response->getInfo('error'));
  514. $this->expectException(TransportExceptionInterface::class);
  515. $response->getContent();
  516. }
  517. public function testOnProgressError()
  518. {
  519. $client = $this->getHttpClient(__FUNCTION__);
  520. $response = $client->request('GET', 'http://localhost:8057/timeout-body', [
  521. 'on_progress' => function ($dlNow) {
  522. if (0 < $dlNow) {
  523. throw new \Error('BUG.');
  524. }
  525. },
  526. ]);
  527. try {
  528. foreach ($client->stream([$response]) as $chunk) {
  529. }
  530. $this->fail('Error expected');
  531. } catch (\Error $e) {
  532. $this->assertSame('BUG.', $e->getMessage());
  533. }
  534. $this->assertNotNull($response->getInfo('error'));
  535. $this->expectException(TransportExceptionInterface::class);
  536. $response->getContent();
  537. }
  538. public function testResolve()
  539. {
  540. $client = $this->getHttpClient(__FUNCTION__);
  541. $response = $client->request('GET', 'http://symfony.com:8057/', [
  542. 'resolve' => ['symfony.com' => '127.0.0.1'],
  543. ]);
  544. $this->assertSame(200, $response->getStatusCode());
  545. $this->assertSame(200, $client->request('GET', 'http://symfony.com:8057/')->getStatusCode());
  546. $response = null;
  547. $this->expectException(TransportExceptionInterface::class);
  548. $client->request('GET', 'http://symfony.com:8057/', ['timeout' => 1]);
  549. }
  550. public function testIdnResolve()
  551. {
  552. $client = $this->getHttpClient(__FUNCTION__);
  553. $response = $client->request('GET', 'http://0-------------------------------------------------------------0.com:8057/', [
  554. 'resolve' => ['0-------------------------------------------------------------0.com' => '127.0.0.1'],
  555. ]);
  556. $this->assertSame(200, $response->getStatusCode());
  557. $response = $client->request('GET', 'http://Bücher.example:8057/', [
  558. 'resolve' => ['xn--bcher-kva.example' => '127.0.0.1'],
  559. ]);
  560. $this->assertSame(200, $response->getStatusCode());
  561. }
  562. public function testNotATimeout()
  563. {
  564. $client = $this->getHttpClient(__FUNCTION__);
  565. $response = $client->request('GET', 'http://localhost:8057/timeout-header', [
  566. 'timeout' => 0.9,
  567. ]);
  568. sleep(1);
  569. $this->assertSame(200, $response->getStatusCode());
  570. }
  571. public function testTimeoutOnAccess()
  572. {
  573. $client = $this->getHttpClient(__FUNCTION__);
  574. $response = $client->request('GET', 'http://localhost:8057/timeout-header', [
  575. 'timeout' => 0.1,
  576. ]);
  577. $this->expectException(TransportExceptionInterface::class);
  578. $response->getHeaders();
  579. }
  580. public function testTimeoutOnStream()
  581. {
  582. usleep(300000); // wait for the previous test to release the server
  583. $client = $this->getHttpClient(__FUNCTION__);
  584. $response = $client->request('GET', 'http://localhost:8057/timeout-body');
  585. $this->assertSame(200, $response->getStatusCode());
  586. $chunks = $client->stream([$response], 0.2);
  587. $result = [];
  588. foreach ($chunks as $r => $chunk) {
  589. if ($chunk->isTimeout()) {
  590. $result[] = 't';
  591. } else {
  592. $result[] = $chunk->getContent();
  593. }
  594. }
  595. $this->assertSame(['<1>', 't'], $result);
  596. $chunks = $client->stream([$response]);
  597. foreach ($chunks as $r => $chunk) {
  598. $this->assertSame('<2>', $chunk->getContent());
  599. $this->assertSame('<1><2>', $r->getContent());
  600. return;
  601. }
  602. $this->fail('The response should have completed');
  603. }
  604. public function testUncheckedTimeoutThrows()
  605. {
  606. $client = $this->getHttpClient(__FUNCTION__);
  607. $response = $client->request('GET', 'http://localhost:8057/timeout-body');
  608. $chunks = $client->stream([$response], 0.1);
  609. $this->expectException(TransportExceptionInterface::class);
  610. foreach ($chunks as $r => $chunk) {
  611. }
  612. }
  613. public function testTimeoutWithActiveConcurrentStream()
  614. {
  615. $p1 = TestHttpServer::start(8067);
  616. $p2 = TestHttpServer::start(8077);
  617. $client = $this->getHttpClient(__FUNCTION__);
  618. $streamingResponse = $client->request('GET', 'http://localhost:8067/max-duration');
  619. $blockingResponse = $client->request('GET', 'http://localhost:8077/timeout-body', [
  620. 'timeout' => 0.25,
  621. ]);
  622. $this->assertSame(200, $streamingResponse->getStatusCode());
  623. $this->assertSame(200, $blockingResponse->getStatusCode());
  624. $this->expectException(TransportExceptionInterface::class);
  625. try {
  626. $blockingResponse->getContent();
  627. } finally {
  628. $p1->stop();
  629. $p2->stop();
  630. }
  631. }
  632. public function testDestruct()
  633. {
  634. $client = $this->getHttpClient(__FUNCTION__);
  635. $start = microtime(true);
  636. $client->request('GET', 'http://localhost:8057/timeout-long');
  637. $client = null;
  638. $duration = microtime(true) - $start;
  639. $this->assertGreaterThan(1, $duration);
  640. $this->assertLessThan(4, $duration);
  641. }
  642. public function testGetContentAfterDestruct()
  643. {
  644. $client = $this->getHttpClient(__FUNCTION__);
  645. try {
  646. $client->request('GET', 'http://localhost:8057/404');
  647. $this->fail(ClientExceptionInterface::class.' expected');
  648. } catch (ClientExceptionInterface $e) {
  649. $this->assertSame('GET', $e->getResponse()->toArray(false)['REQUEST_METHOD']);
  650. }
  651. }
  652. public function testProxy()
  653. {
  654. $client = $this->getHttpClient(__FUNCTION__);
  655. $response = $client->request('GET', 'http://localhost:8057/', [
  656. 'proxy' => 'http://localhost:8057',
  657. ]);
  658. $body = $response->toArray();
  659. $this->assertSame('localhost:8057', $body['HTTP_HOST']);
  660. $this->assertMatchesRegularExpression('#^http://(localhost|127\.0\.0\.1):8057/$#', $body['REQUEST_URI']);
  661. $response = $client->request('GET', 'http://localhost:8057/', [
  662. 'proxy' => 'http://foo:b%3Dar@localhost:8057',
  663. ]);
  664. $body = $response->toArray();
  665. $this->assertSame('Basic Zm9vOmI9YXI=', $body['HTTP_PROXY_AUTHORIZATION']);
  666. }
  667. public function testNoProxy()
  668. {
  669. putenv('no_proxy='.$_SERVER['no_proxy'] = 'example.com, localhost');
  670. try {
  671. $client = $this->getHttpClient(__FUNCTION__);
  672. $response = $client->request('GET', 'http://localhost:8057/', [
  673. 'proxy' => 'http://localhost:8057',
  674. ]);
  675. $body = $response->toArray();
  676. $this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']);
  677. $this->assertSame('/', $body['REQUEST_URI']);
  678. $this->assertSame('GET', $body['REQUEST_METHOD']);
  679. } finally {
  680. putenv('no_proxy');
  681. unset($_SERVER['no_proxy']);
  682. }
  683. }
  684. /**
  685. * @requires extension zlib
  686. */
  687. public function testAutoEncodingRequest()
  688. {
  689. $client = $this->getHttpClient(__FUNCTION__);
  690. $response = $client->request('GET', 'http://localhost:8057');
  691. $this->assertSame(200, $response->getStatusCode());
  692. $headers = $response->getHeaders();
  693. $this->assertSame(['Accept-Encoding'], $headers['vary']);
  694. $this->assertStringContainsString('gzip', $headers['content-encoding'][0]);
  695. $body = $response->toArray();
  696. $this->assertStringContainsString('gzip', $body['HTTP_ACCEPT_ENCODING']);
  697. }
  698. public function testBaseUri()
  699. {
  700. $client = $this->getHttpClient(__FUNCTION__);
  701. $response = $client->request('GET', '../404', [
  702. 'base_uri' => 'http://localhost:8057/abc/',
  703. ]);
  704. $this->assertSame(404, $response->getStatusCode());
  705. $this->assertSame(['application/json'], $response->getHeaders(false)['content-type']);
  706. }
  707. public function testQuery()
  708. {
  709. $client = $this->getHttpClient(__FUNCTION__);
  710. $response = $client->request('GET', 'http://localhost:8057/?a=a', [
  711. 'query' => ['b' => 'b'],
  712. ]);
  713. $body = $response->toArray();
  714. $this->assertSame('GET', $body['REQUEST_METHOD']);
  715. $this->assertSame('/?a=a&b=b', $body['REQUEST_URI']);
  716. }
  717. public function testInformationalResponse()
  718. {
  719. $client = $this->getHttpClient(__FUNCTION__);
  720. $response = $client->request('GET', 'http://localhost:8057/103');
  721. $this->assertSame('Here the body', $response->getContent());
  722. $this->assertSame(200, $response->getStatusCode());
  723. }
  724. public function testInformationalResponseStream()
  725. {
  726. $client = $this->getHttpClient(__FUNCTION__);
  727. $response = $client->request('GET', 'http://localhost:8057/103');
  728. $chunks = [];
  729. foreach ($client->stream($response) as $chunk) {
  730. $chunks[] = $chunk;
  731. }
  732. $this->assertSame(103, $chunks[0]->getInformationalStatus()[0]);
  733. $this->assertSame(['</style.css>; rel=preload; as=style', '</script.js>; rel=preload; as=script'], $chunks[0]->getInformationalStatus()[1]['link']);
  734. $this->assertTrue($chunks[1]->isFirst());
  735. $this->assertSame('Here the body', $chunks[2]->getContent());
  736. $this->assertTrue($chunks[3]->isLast());
  737. $this->assertNull($chunks[3]->getInformationalStatus());
  738. $this->assertSame(['date', 'content-length'], array_keys($response->getHeaders()));
  739. $this->assertContains('Link: </style.css>; rel=preload; as=style', $response->getInfo('response_headers'));
  740. }
  741. /**
  742. * @requires extension zlib
  743. */
  744. public function testUserlandEncodingRequest()
  745. {
  746. $client = $this->getHttpClient(__FUNCTION__);
  747. $response = $client->request('GET', 'http://localhost:8057', [
  748. 'headers' => ['Accept-Encoding' => 'gzip'],
  749. ]);
  750. $headers = $response->getHeaders();
  751. $this->assertSame(['Accept-Encoding'], $headers['vary']);
  752. $this->assertStringContainsString('gzip', $headers['content-encoding'][0]);
  753. $body = $response->getContent();
  754. $this->assertSame("\x1F", $body[0]);
  755. $body = json_decode(gzdecode($body), true);
  756. $this->assertSame('gzip', $body['HTTP_ACCEPT_ENCODING']);
  757. }
  758. /**
  759. * @requires extension zlib
  760. */
  761. public function testGzipBroken()
  762. {
  763. $client = $this->getHttpClient(__FUNCTION__);
  764. $response = $client->request('GET', 'http://localhost:8057/gzip-broken');
  765. $this->expectException(TransportExceptionInterface::class);
  766. $response->getContent();
  767. }
  768. public function testMaxDuration()
  769. {
  770. $client = $this->getHttpClient(__FUNCTION__);
  771. $response = $client->request('GET', 'http://localhost:8057/max-duration', [
  772. 'max_duration' => 0.1,
  773. ]);
  774. $start = microtime(true);
  775. try {
  776. $response->getContent();
  777. } catch (TransportExceptionInterface $e) {
  778. $this->addToAssertionCount(1);
  779. }
  780. $duration = microtime(true) - $start;
  781. $this->assertLessThan(10, $duration);
  782. }
  783. }