CurlFactoryTest.php 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  1. <?php
  2. namespace GuzzleHttp\Test\Handler;
  3. use GuzzleHttp\Exception\ConnectException;
  4. use GuzzleHttp\Exception\RequestException;
  5. use GuzzleHttp\Handler;
  6. use GuzzleHttp\Handler\CurlFactory;
  7. use GuzzleHttp\Handler\EasyHandle;
  8. use GuzzleHttp\Promise as P;
  9. use GuzzleHttp\Psr7;
  10. use GuzzleHttp\Tests\Helpers;
  11. use GuzzleHttp\Tests\Server;
  12. use GuzzleHttp\TransferStats;
  13. use PHPUnit\Framework\TestCase;
  14. use Psr\Http\Message\ResponseInterface;
  15. /**
  16. * @covers \GuzzleHttp\Handler\CurlFactory
  17. */
  18. class CurlFactoryTest extends TestCase
  19. {
  20. public static function setUpBeforeClass(): void
  21. {
  22. $_SERVER['curl_test'] = true;
  23. unset($_SERVER['_curl']);
  24. }
  25. public static function tearDownAfterClass(): void
  26. {
  27. unset($_SERVER['_curl'], $_SERVER['curl_test']);
  28. }
  29. public function testCreatesCurlHandle()
  30. {
  31. Server::flush();
  32. Server::enqueue([
  33. new Psr7\Response(200, [
  34. 'Foo' => 'Bar',
  35. 'Baz' => 'bam',
  36. 'Content-Length' => 2,
  37. ], 'hi'),
  38. ]);
  39. $stream = Psr7\Utils::streamFor();
  40. $request = new Psr7\Request('PUT', Server::$url, [
  41. 'Hi' => ' 123',
  42. 'Content-Length' => '7',
  43. ], 'testing');
  44. $f = new Handler\CurlFactory(3);
  45. $result = $f->create($request, ['sink' => $stream]);
  46. self::assertInstanceOf(EasyHandle::class, $result);
  47. if (\PHP_VERSION_ID >= 80000) {
  48. self::assertInstanceOf(\CurlHandle::class, $result->handle);
  49. } else {
  50. self::assertIsResource($result->handle);
  51. }
  52. self::assertIsArray($result->headers);
  53. self::assertSame($stream, $result->sink);
  54. \curl_close($result->handle);
  55. self::assertSame('PUT', $_SERVER['_curl'][\CURLOPT_CUSTOMREQUEST]);
  56. self::assertSame(
  57. 'http://127.0.0.1:8126/',
  58. $_SERVER['_curl'][\CURLOPT_URL]
  59. );
  60. // Sends via post fields when the request is small enough
  61. self::assertSame('testing', $_SERVER['_curl'][\CURLOPT_POSTFIELDS]);
  62. self::assertEquals(0, $_SERVER['_curl'][\CURLOPT_RETURNTRANSFER]);
  63. self::assertEquals(0, $_SERVER['_curl'][\CURLOPT_HEADER]);
  64. self::assertSame(300, $_SERVER['_curl'][\CURLOPT_CONNECTTIMEOUT]);
  65. self::assertInstanceOf('Closure', $_SERVER['_curl'][\CURLOPT_HEADERFUNCTION]);
  66. if (\defined('CURLOPT_PROTOCOLS')) {
  67. self::assertSame(
  68. \CURLPROTO_HTTP | \CURLPROTO_HTTPS,
  69. $_SERVER['_curl'][\CURLOPT_PROTOCOLS]
  70. );
  71. }
  72. self::assertContains('Expect:', $_SERVER['_curl'][\CURLOPT_HTTPHEADER]);
  73. self::assertContains('Accept:', $_SERVER['_curl'][\CURLOPT_HTTPHEADER]);
  74. self::assertContains('Content-Type:', $_SERVER['_curl'][\CURLOPT_HTTPHEADER]);
  75. self::assertContains('Hi: 123', $_SERVER['_curl'][\CURLOPT_HTTPHEADER]);
  76. self::assertContains('Host: 127.0.0.1:8126', $_SERVER['_curl'][\CURLOPT_HTTPHEADER]);
  77. }
  78. public function testSendsHeadRequests()
  79. {
  80. Server::flush();
  81. Server::enqueue([new Psr7\Response()]);
  82. $a = new Handler\CurlMultiHandler();
  83. $response = $a(new Psr7\Request('HEAD', Server::$url), []);
  84. $response->wait();
  85. self::assertTrue($_SERVER['_curl'][\CURLOPT_NOBODY]);
  86. $checks = [\CURLOPT_READFUNCTION, \CURLOPT_FILE, \CURLOPT_INFILE];
  87. foreach ($checks as $check) {
  88. self::assertArrayNotHasKey($check, $_SERVER['_curl']);
  89. }
  90. self::assertEquals('HEAD', Server::received()[0]->getMethod());
  91. }
  92. public function testCanAddCustomCurlOptions()
  93. {
  94. Server::flush();
  95. Server::enqueue([new Psr7\Response()]);
  96. $a = new Handler\CurlMultiHandler();
  97. $req = new Psr7\Request('GET', Server::$url);
  98. $a($req, ['curl' => [\CURLOPT_LOW_SPEED_LIMIT => 10]]);
  99. self::assertEquals(10, $_SERVER['_curl'][\CURLOPT_LOW_SPEED_LIMIT]);
  100. }
  101. public function testCanChangeCurlOptions()
  102. {
  103. Server::flush();
  104. Server::enqueue([new Psr7\Response()]);
  105. $a = new Handler\CurlMultiHandler();
  106. $req = new Psr7\Request('GET', Server::$url);
  107. $a($req, ['curl' => [\CURLOPT_HTTP_VERSION => \CURL_HTTP_VERSION_1_0]]);
  108. self::assertEquals(\CURL_HTTP_VERSION_1_0, $_SERVER['_curl'][\CURLOPT_HTTP_VERSION]);
  109. }
  110. public function testValidatesVerify()
  111. {
  112. $f = new Handler\CurlFactory(3);
  113. $this->expectException(\InvalidArgumentException::class);
  114. $this->expectExceptionMessage('SSL CA bundle not found: /does/not/exist');
  115. $f->create(new Psr7\Request('GET', Server::$url), ['verify' => '/does/not/exist']);
  116. }
  117. public function testCanSetVerifyToFile()
  118. {
  119. $f = new Handler\CurlFactory(3);
  120. $f->create(new Psr7\Request('GET', 'http://foo.com'), ['verify' => __FILE__]);
  121. self::assertEquals(__FILE__, $_SERVER['_curl'][\CURLOPT_CAINFO]);
  122. self::assertEquals(2, $_SERVER['_curl'][\CURLOPT_SSL_VERIFYHOST]);
  123. self::assertTrue($_SERVER['_curl'][\CURLOPT_SSL_VERIFYPEER]);
  124. }
  125. public function testCanSetVerifyToDir()
  126. {
  127. $f = new Handler\CurlFactory(3);
  128. $f->create(new Psr7\Request('GET', 'http://foo.com'), ['verify' => __DIR__]);
  129. self::assertEquals(__DIR__, $_SERVER['_curl'][\CURLOPT_CAPATH]);
  130. self::assertEquals(2, $_SERVER['_curl'][\CURLOPT_SSL_VERIFYHOST]);
  131. self::assertTrue($_SERVER['_curl'][\CURLOPT_SSL_VERIFYPEER]);
  132. }
  133. public function testAddsVerifyAsTrue()
  134. {
  135. $f = new Handler\CurlFactory(3);
  136. $f->create(new Psr7\Request('GET', Server::$url), ['verify' => true]);
  137. self::assertEquals(2, $_SERVER['_curl'][\CURLOPT_SSL_VERIFYHOST]);
  138. self::assertTrue($_SERVER['_curl'][\CURLOPT_SSL_VERIFYPEER]);
  139. self::assertArrayNotHasKey(\CURLOPT_CAINFO, $_SERVER['_curl']);
  140. }
  141. public function testCanDisableVerify()
  142. {
  143. $f = new Handler\CurlFactory(3);
  144. $f->create(new Psr7\Request('GET', Server::$url), ['verify' => false]);
  145. self::assertEquals(0, $_SERVER['_curl'][\CURLOPT_SSL_VERIFYHOST]);
  146. self::assertFalse($_SERVER['_curl'][\CURLOPT_SSL_VERIFYPEER]);
  147. }
  148. public function testAddsProxy()
  149. {
  150. $f = new Handler\CurlFactory(3);
  151. $f->create(new Psr7\Request('GET', Server::$url), ['proxy' => 'http://bar.com']);
  152. self::assertEquals('http://bar.com', $_SERVER['_curl'][\CURLOPT_PROXY]);
  153. }
  154. public function testAddsViaScheme()
  155. {
  156. $f = new Handler\CurlFactory(3);
  157. $f->create(new Psr7\Request('GET', Server::$url), [
  158. 'proxy' => ['http' => 'http://bar.com', 'https' => 'https://t'],
  159. ]);
  160. self::assertEquals('http://bar.com', $_SERVER['_curl'][\CURLOPT_PROXY]);
  161. $this->checkNoProxyForHost('http://test.test.com', ['test.test.com'], false);
  162. $this->checkNoProxyForHost('http://test.test.com', ['.test.com'], false);
  163. $this->checkNoProxyForHost('http://test.test.com', ['*.test.com'], true);
  164. $this->checkNoProxyForHost('http://test.test.com', ['*'], false);
  165. $this->checkNoProxyForHost('http://127.0.0.1', ['127.0.0.*'], true);
  166. }
  167. private function checkNoProxyForHost($url, $noProxy, $assertUseProxy)
  168. {
  169. $f = new Handler\CurlFactory(3);
  170. $f->create(new Psr7\Request('GET', $url), [
  171. 'proxy' => [
  172. 'http' => 'http://bar.com',
  173. 'https' => 'https://t',
  174. 'no' => $noProxy,
  175. ],
  176. ]);
  177. if ($assertUseProxy) {
  178. self::assertArrayHasKey(\CURLOPT_PROXY, $_SERVER['_curl']);
  179. } else {
  180. self::assertArrayNotHasKey(\CURLOPT_PROXY, $_SERVER['_curl']);
  181. }
  182. }
  183. public function testUsesProxy()
  184. {
  185. Server::flush();
  186. Server::enqueue([
  187. new Psr7\Response(200, [
  188. 'Foo' => 'Bar',
  189. 'Baz' => 'bam',
  190. 'Content-Length' => 2,
  191. ], 'hi'),
  192. ]);
  193. $handler = new Handler\CurlMultiHandler();
  194. $request = new Psr7\Request('GET', 'http://www.example.com', [], null, '1.0');
  195. $promise = $handler($request, [
  196. 'proxy' => Server::$url,
  197. ]);
  198. $response = $promise->wait();
  199. self::assertSame(200, $response->getStatusCode());
  200. self::assertSame('Bar', $response->getHeaderLine('Foo'));
  201. self::assertSame('2', $response->getHeaderLine('Content-Length'));
  202. self::assertSame('hi', (string) $response->getBody());
  203. }
  204. public function testValidatesCryptoMethodInvalidMethod()
  205. {
  206. $f = new Handler\CurlFactory(3);
  207. $this->expectException(\InvalidArgumentException::class);
  208. $this->expectExceptionMessage('Invalid crypto_method request option: unknown version provided');
  209. $f->create(new Psr7\Request('GET', Server::$url), ['crypto_method' => 123]);
  210. }
  211. public function testAddsCryptoMethodTls10()
  212. {
  213. $f = new Handler\CurlFactory(3);
  214. $f->create(new Psr7\Request('GET', Server::$url), ['crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT]);
  215. self::assertEquals(\CURL_SSLVERSION_TLSv1_0, $_SERVER['_curl'][\CURLOPT_SSLVERSION]);
  216. }
  217. public function testAddsCryptoMethodTls11()
  218. {
  219. $f = new Handler\CurlFactory(3);
  220. $f->create(new Psr7\Request('GET', Server::$url), ['crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT]);
  221. self::assertEquals(\CURL_SSLVERSION_TLSv1_1, $_SERVER['_curl'][\CURLOPT_SSLVERSION]);
  222. }
  223. public function testAddsCryptoMethodTls12()
  224. {
  225. $f = new Handler\CurlFactory(3);
  226. $f->create(new Psr7\Request('GET', Server::$url), ['crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT]);
  227. self::assertEquals(\CURL_SSLVERSION_TLSv1_2, $_SERVER['_curl'][\CURLOPT_SSLVERSION]);
  228. }
  229. /**
  230. * @requires PHP >= 7.4
  231. */
  232. public function testAddsCryptoMethodTls13()
  233. {
  234. $f = new Handler\CurlFactory(3);
  235. $f->create(new Psr7\Request('GET', Server::$url), ['crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT]);
  236. self::assertEquals(\CURL_SSLVERSION_TLSv1_3, $_SERVER['_curl'][\CURLOPT_SSLVERSION]);
  237. }
  238. public function testValidatesSslKey()
  239. {
  240. $f = new Handler\CurlFactory(3);
  241. $this->expectException(\InvalidArgumentException::class);
  242. $this->expectExceptionMessage('SSL private key not found: /does/not/exist');
  243. $f->create(new Psr7\Request('GET', Server::$url), ['ssl_key' => '/does/not/exist']);
  244. }
  245. public function testAddsSslKey()
  246. {
  247. $f = new Handler\CurlFactory(3);
  248. $f->create(new Psr7\Request('GET', Server::$url), ['ssl_key' => __FILE__]);
  249. self::assertEquals(__FILE__, $_SERVER['_curl'][\CURLOPT_SSLKEY]);
  250. }
  251. public function testAddsSslKeyWithPassword()
  252. {
  253. $f = new Handler\CurlFactory(3);
  254. $f->create(new Psr7\Request('GET', Server::$url), ['ssl_key' => [__FILE__, 'test']]);
  255. self::assertEquals(__FILE__, $_SERVER['_curl'][\CURLOPT_SSLKEY]);
  256. self::assertEquals('test', $_SERVER['_curl'][\CURLOPT_SSLKEYPASSWD]);
  257. }
  258. public function testAddsSslKeyWhenUsingArraySyntaxButNoPassword()
  259. {
  260. $f = new Handler\CurlFactory(3);
  261. $f->create(new Psr7\Request('GET', Server::$url), ['ssl_key' => [__FILE__]]);
  262. self::assertEquals(__FILE__, $_SERVER['_curl'][\CURLOPT_SSLKEY]);
  263. }
  264. public function testValidatesCert()
  265. {
  266. $f = new Handler\CurlFactory(3);
  267. $this->expectException(\InvalidArgumentException::class);
  268. $this->expectExceptionMessage('SSL certificate not found: /does/not/exist');
  269. $f->create(new Psr7\Request('GET', Server::$url), ['cert' => '/does/not/exist']);
  270. }
  271. public function testAddsCert()
  272. {
  273. $f = new Handler\CurlFactory(3);
  274. $f->create(new Psr7\Request('GET', Server::$url), ['cert' => __FILE__]);
  275. self::assertEquals(__FILE__, $_SERVER['_curl'][\CURLOPT_SSLCERT]);
  276. }
  277. public function testAddsCertWithPassword()
  278. {
  279. $f = new Handler\CurlFactory(3);
  280. $f->create(new Psr7\Request('GET', Server::$url), ['cert' => [__FILE__, 'test']]);
  281. self::assertEquals(__FILE__, $_SERVER['_curl'][\CURLOPT_SSLCERT]);
  282. self::assertEquals('test', $_SERVER['_curl'][\CURLOPT_SSLCERTPASSWD]);
  283. }
  284. public function testAddsDerCert()
  285. {
  286. $certFile = tempnam(sys_get_temp_dir(), 'mock_test_cert');
  287. rename($certFile, $certFile .= '.der');
  288. try {
  289. $f = new Handler\CurlFactory(3);
  290. $f->create(new Psr7\Request('GET', Server::$url), ['cert' => $certFile]);
  291. self::assertArrayHasKey(\CURLOPT_SSLCERTTYPE, $_SERVER['_curl']);
  292. self::assertEquals('DER', $_SERVER['_curl'][\CURLOPT_SSLCERTTYPE]);
  293. } finally {
  294. @\unlink($certFile);
  295. }
  296. }
  297. public function testAddsP12Cert()
  298. {
  299. $certFile = tempnam(sys_get_temp_dir(), 'mock_test_cert');
  300. rename($certFile, $certFile .= '.p12');
  301. try {
  302. $f = new Handler\CurlFactory(3);
  303. $f->create(new Psr7\Request('GET', Server::$url), ['cert' => $certFile]);
  304. self::assertArrayHasKey(\CURLOPT_SSLCERTTYPE, $_SERVER['_curl']);
  305. self::assertEquals('P12', $_SERVER['_curl'][\CURLOPT_SSLCERTTYPE]);
  306. } finally {
  307. @\unlink($certFile);
  308. }
  309. }
  310. public function testValidatesProgress()
  311. {
  312. $f = new Handler\CurlFactory(3);
  313. $this->expectException(\InvalidArgumentException::class);
  314. $this->expectExceptionMessage('progress client option must be callable');
  315. $f->create(new Psr7\Request('GET', Server::$url), ['progress' => 'foo']);
  316. }
  317. public function testEmitsDebugInfoToStream()
  318. {
  319. $res = \fopen('php://temp', 'r+');
  320. Server::flush();
  321. Server::enqueue([new Psr7\Response()]);
  322. $a = new Handler\CurlMultiHandler();
  323. $response = $a(new Psr7\Request('HEAD', Server::$url), ['debug' => $res]);
  324. $response->wait();
  325. \rewind($res);
  326. $output = \str_replace("\r", '', \stream_get_contents($res));
  327. self::assertStringContainsString('> HEAD / HTTP/1.1', $output);
  328. self::assertStringContainsString('< HTTP/1.1 200', $output);
  329. \fclose($res);
  330. }
  331. public function testEmitsProgressToFunction()
  332. {
  333. Server::flush();
  334. Server::enqueue([new Psr7\Response()]);
  335. $a = new Handler\CurlMultiHandler();
  336. $called = [];
  337. $request = new Psr7\Request('HEAD', Server::$url);
  338. $response = $a($request, [
  339. 'progress' => static function (...$args) use (&$called) {
  340. $called[] = $args;
  341. },
  342. ]);
  343. $response->wait();
  344. self::assertNotEmpty($called);
  345. foreach ($called as $call) {
  346. self::assertCount(4, $call);
  347. }
  348. }
  349. private function addDecodeResponse($withEncoding = true)
  350. {
  351. $content = \gzencode('test');
  352. $headers = ['Content-Length' => \strlen($content)];
  353. if ($withEncoding) {
  354. $headers['Content-Encoding'] = 'gzip';
  355. }
  356. $response = new Psr7\Response(200, $headers, $content);
  357. Server::flush();
  358. Server::enqueue([$response]);
  359. return $content;
  360. }
  361. public function testDecodesGzippedResponses()
  362. {
  363. $this->addDecodeResponse();
  364. $handler = new Handler\CurlMultiHandler();
  365. $request = new Psr7\Request('GET', Server::$url);
  366. $response = $handler($request, ['decode_content' => true]);
  367. $response = $response->wait();
  368. self::assertEquals('test', (string) $response->getBody());
  369. self::assertEquals('', $_SERVER['_curl'][\CURLOPT_ENCODING]);
  370. $sent = Server::received()[0];
  371. self::assertFalse($sent->hasHeader('Accept-Encoding'));
  372. }
  373. public function testReportsOriginalSizeAndContentEncodingAfterDecoding()
  374. {
  375. $this->addDecodeResponse();
  376. $handler = new Handler\CurlMultiHandler();
  377. $request = new Psr7\Request('GET', Server::$url);
  378. $response = $handler($request, ['decode_content' => true]);
  379. $response = $response->wait();
  380. self::assertSame(
  381. 'gzip',
  382. $response->getHeaderLine('x-encoded-content-encoding')
  383. );
  384. self::assertSame(
  385. \strlen(\gzencode('test')),
  386. (int) $response->getHeaderLine('x-encoded-content-length')
  387. );
  388. }
  389. public function testDecodesGzippedResponsesWithHeader()
  390. {
  391. $this->addDecodeResponse();
  392. $handler = new Handler\CurlMultiHandler();
  393. $request = new Psr7\Request('GET', Server::$url, ['Accept-Encoding' => 'gzip']);
  394. $response = $handler($request, ['decode_content' => true]);
  395. $response = $response->wait();
  396. self::assertEquals('gzip', $_SERVER['_curl'][\CURLOPT_ENCODING]);
  397. $sent = Server::received()[0];
  398. self::assertEquals('gzip', $sent->getHeaderLine('Accept-Encoding'));
  399. self::assertEquals('test', (string) $response->getBody());
  400. self::assertFalse($response->hasHeader('content-encoding'));
  401. self::assertTrue(
  402. !$response->hasHeader('content-length')
  403. || $response->getHeaderLine('content-length') == $response->getBody()->getSize()
  404. );
  405. }
  406. /**
  407. * https://github.com/guzzle/guzzle/issues/2799
  408. */
  409. public function testDecodesGzippedResponsesWithHeaderForHeadRequest()
  410. {
  411. $this->addDecodeResponse();
  412. $handler = new Handler\CurlMultiHandler();
  413. $request = new Psr7\Request('HEAD', Server::$url, ['Accept-Encoding' => 'gzip']);
  414. $response = $handler($request, ['decode_content' => true]);
  415. $response = $response->wait();
  416. self::assertEquals('gzip', $_SERVER['_curl'][\CURLOPT_ENCODING]);
  417. $sent = Server::received()[0];
  418. self::assertEquals('gzip', $sent->getHeaderLine('Accept-Encoding'));
  419. // Verify that the content-length matches the encoded size.
  420. self::assertTrue(
  421. !$response->hasHeader('content-length')
  422. || $response->getHeaderLine('content-length') == \strlen(\gzencode('test'))
  423. );
  424. }
  425. public function testDoesNotForceDecode()
  426. {
  427. $content = $this->addDecodeResponse();
  428. $handler = new Handler\CurlMultiHandler();
  429. $request = new Psr7\Request('GET', Server::$url);
  430. $response = $handler($request, ['decode_content' => false]);
  431. $response = $response->wait();
  432. $sent = Server::received()[0];
  433. self::assertFalse($sent->hasHeader('Accept-Encoding'));
  434. self::assertEquals($content, (string) $response->getBody());
  435. }
  436. public function testProtocolVersion()
  437. {
  438. Server::flush();
  439. Server::enqueue([new Psr7\Response()]);
  440. $a = new Handler\CurlMultiHandler();
  441. $request = new Psr7\Request('GET', Server::$url, [], null, '1.0');
  442. $a($request, []);
  443. self::assertEquals(\CURL_HTTP_VERSION_1_0, $_SERVER['_curl'][\CURLOPT_HTTP_VERSION]);
  444. }
  445. public function testSavesToStream()
  446. {
  447. $stream = \fopen('php://memory', 'r+');
  448. $this->addDecodeResponse();
  449. $handler = new Handler\CurlMultiHandler();
  450. $request = new Psr7\Request('GET', Server::$url);
  451. $response = $handler($request, [
  452. 'decode_content' => true,
  453. 'sink' => $stream,
  454. ]);
  455. $response->wait();
  456. \rewind($stream);
  457. self::assertEquals('test', \stream_get_contents($stream));
  458. }
  459. public function testSavesToGuzzleStream()
  460. {
  461. $stream = Psr7\Utils::streamFor();
  462. $this->addDecodeResponse();
  463. $handler = new Handler\CurlMultiHandler();
  464. $request = new Psr7\Request('GET', Server::$url);
  465. $response = $handler($request, [
  466. 'decode_content' => true,
  467. 'sink' => $stream,
  468. ]);
  469. $response->wait();
  470. self::assertEquals('test', (string) $stream);
  471. }
  472. public function testSavesToFileOnDisk()
  473. {
  474. $tmpfile = \tempnam(\sys_get_temp_dir(), 'testfile');
  475. $this->addDecodeResponse();
  476. $handler = new Handler\CurlMultiHandler();
  477. $request = new Psr7\Request('GET', Server::$url);
  478. $response = $handler($request, [
  479. 'decode_content' => true,
  480. 'sink' => $tmpfile,
  481. ]);
  482. $response->wait();
  483. self::assertStringEqualsFile($tmpfile, 'test');
  484. @\unlink($tmpfile);
  485. }
  486. public function testDoesNotAddMultipleContentLengthHeaders()
  487. {
  488. $this->addDecodeResponse();
  489. $handler = new Handler\CurlMultiHandler();
  490. $request = new Psr7\Request('PUT', Server::$url, ['Content-Length' => 3], 'foo');
  491. $response = $handler($request, []);
  492. $response->wait();
  493. $sent = Server::received()[0];
  494. self::assertEquals(3, $sent->getHeaderLine('Content-Length'));
  495. self::assertFalse($sent->hasHeader('Transfer-Encoding'));
  496. self::assertEquals('foo', (string) $sent->getBody());
  497. }
  498. public function testSendsPostWithNoBodyOrDefaultContentType()
  499. {
  500. Server::flush();
  501. Server::enqueue([new Psr7\Response()]);
  502. $handler = new Handler\CurlMultiHandler();
  503. $request = new Psr7\Request('POST', Server::$url);
  504. $response = $handler($request, []);
  505. $response->wait();
  506. $received = Server::received()[0];
  507. self::assertEquals('POST', $received->getMethod());
  508. self::assertFalse($received->hasHeader('content-type'));
  509. self::assertSame('0', $received->getHeaderLine('content-length'));
  510. }
  511. public function testFailsWhenCannotRewindRetryAfterNoResponse()
  512. {
  513. $factory = new Handler\CurlFactory(1);
  514. $stream = Psr7\Utils::streamFor('abc');
  515. $stream->read(1);
  516. $stream = new Psr7\NoSeekStream($stream);
  517. $request = new Psr7\Request('PUT', Server::$url, [], $stream);
  518. $fn = static function ($request, $options) use (&$fn, $factory) {
  519. $easy = $factory->create($request, $options);
  520. return Handler\CurlFactory::finish($fn, $easy, $factory);
  521. };
  522. $this->expectException(RequestException::class);
  523. $this->expectExceptionMessage('but attempting to rewind the request body failed');
  524. $fn($request, [])->wait();
  525. }
  526. public function testRetriesWhenBodyCanBeRewound()
  527. {
  528. $callHandler = $called = false;
  529. $fn = static function ($r, $options) use (&$callHandler) {
  530. $callHandler = true;
  531. return P\Create::promiseFor(new Psr7\Response());
  532. };
  533. $bd = Psr7\FnStream::decorate(Psr7\Utils::streamFor('test'), [
  534. 'tell' => static function () {
  535. return 1;
  536. },
  537. 'rewind' => static function () use (&$called) {
  538. $called = true;
  539. },
  540. ]);
  541. $factory = new Handler\CurlFactory(1);
  542. $req = new Psr7\Request('PUT', Server::$url, [], $bd);
  543. $easy = $factory->create($req, []);
  544. $res = Handler\CurlFactory::finish($fn, $easy, $factory);
  545. $res = $res->wait();
  546. self::assertTrue($callHandler);
  547. self::assertTrue($called);
  548. self::assertEquals('200', $res->getStatusCode());
  549. }
  550. public function testFailsWhenRetryMoreThanThreeTimes()
  551. {
  552. $factory = new Handler\CurlFactory(1);
  553. $call = 0;
  554. $fn = static function ($request, $options) use (&$mock, &$call, $factory) {
  555. ++$call;
  556. $easy = $factory->create($request, $options);
  557. return Handler\CurlFactory::finish($mock, $easy, $factory);
  558. };
  559. $mock = new Handler\MockHandler([$fn, $fn, $fn]);
  560. $p = $mock(new Psr7\Request('PUT', Server::$url, [], 'test'), []);
  561. $p->wait(false);
  562. self::assertEquals(3, $call);
  563. $this->expectException(RequestException::class);
  564. $this->expectExceptionMessage('The cURL request was retried 3 times');
  565. $p->wait(true);
  566. }
  567. public function testHandles100Continue()
  568. {
  569. Server::flush();
  570. Server::enqueue([
  571. new Psr7\Response(200, ['Test' => 'Hello', 'Content-Length' => 4], 'test'),
  572. ]);
  573. $request = new Psr7\Request('PUT', Server::$url, [
  574. 'Expect' => '100-Continue',
  575. ], 'test');
  576. $handler = new Handler\CurlMultiHandler();
  577. $response = $handler($request, [])->wait();
  578. self::assertSame(200, $response->getStatusCode());
  579. self::assertSame('OK', $response->getReasonPhrase());
  580. self::assertSame('Hello', $response->getHeaderLine('Test'));
  581. self::assertSame('4', $response->getHeaderLine('Content-Length'));
  582. self::assertSame('test', (string) $response->getBody());
  583. }
  584. public function testCreatesConnectException()
  585. {
  586. $m = new \ReflectionMethod(CurlFactory::class, 'finishError');
  587. $m->setAccessible(true);
  588. $factory = new Handler\CurlFactory(1);
  589. $easy = $factory->create(new Psr7\Request('GET', Server::$url), []);
  590. $easy->errno = \CURLE_COULDNT_CONNECT;
  591. $response = $m->invoke(
  592. null,
  593. static function () {
  594. },
  595. $easy,
  596. $factory
  597. );
  598. $this->expectException(ConnectException::class);
  599. $response->wait();
  600. }
  601. public function testAddsTimeouts()
  602. {
  603. $f = new Handler\CurlFactory(3);
  604. $f->create(new Psr7\Request('GET', Server::$url), [
  605. 'timeout' => 0.1,
  606. 'connect_timeout' => 0.2,
  607. ]);
  608. self::assertEquals(100, $_SERVER['_curl'][\CURLOPT_TIMEOUT_MS]);
  609. self::assertEquals(200, $_SERVER['_curl'][\CURLOPT_CONNECTTIMEOUT_MS]);
  610. }
  611. public function testAddsStreamingBody()
  612. {
  613. $f = new Handler\CurlFactory(3);
  614. $bd = Psr7\FnStream::decorate(Psr7\Utils::streamFor('foo'), [
  615. 'getSize' => static function () {
  616. return null;
  617. },
  618. ]);
  619. $request = new Psr7\Request('PUT', Server::$url, [], $bd);
  620. $f->create($request, []);
  621. self::assertEquals(1, $_SERVER['_curl'][\CURLOPT_UPLOAD]);
  622. self::assertIsCallable($_SERVER['_curl'][\CURLOPT_READFUNCTION]);
  623. }
  624. public function testEnsuresDirExistsBeforeThrowingWarning()
  625. {
  626. $f = new Handler\CurlFactory(3);
  627. $this->expectException(\RuntimeException::class);
  628. $this->expectExceptionMessage('Directory /does/not/exist/so does not exist for sink value of /does/not/exist/so/error.txt');
  629. $f->create(new Psr7\Request('GET', Server::$url), [
  630. 'sink' => '/does/not/exist/so/error.txt',
  631. ]);
  632. }
  633. public function testClosesIdleHandles()
  634. {
  635. $f = new Handler\CurlFactory(3);
  636. $req = new Psr7\Request('GET', Server::$url);
  637. $easy = $f->create($req, []);
  638. $h1 = $easy->handle;
  639. $f->release($easy);
  640. self::assertCount(1, Helpers::readObjectAttribute($f, 'handles'));
  641. $easy = $f->create($req, []);
  642. self::assertSame($easy->handle, $h1);
  643. $easy2 = $f->create($req, []);
  644. $easy3 = $f->create($req, []);
  645. $easy4 = $f->create($req, []);
  646. $f->release($easy);
  647. self::assertCount(1, Helpers::readObjectAttribute($f, 'handles'));
  648. $f->release($easy2);
  649. self::assertCount(2, Helpers::readObjectAttribute($f, 'handles'));
  650. $f->release($easy3);
  651. self::assertCount(3, Helpers::readObjectAttribute($f, 'handles'));
  652. $f->release($easy4);
  653. self::assertCount(3, Helpers::readObjectAttribute($f, 'handles'));
  654. }
  655. public function testRejectsPromiseWhenCreateResponseFails()
  656. {
  657. Server::flush();
  658. Server::enqueueRaw(999, 'Incorrect', ['X-Foo' => 'bar'], 'abc 123');
  659. $req = new Psr7\Request('GET', Server::$url);
  660. $handler = new Handler\CurlHandler();
  661. $promise = $handler($req, []);
  662. $this->expectException(\GuzzleHttp\Exception\RequestException::class);
  663. $this->expectExceptionMessage('An error was encountered while creating the response');
  664. $promise->wait();
  665. }
  666. public function testEnsuresOnHeadersIsCallable()
  667. {
  668. $req = new Psr7\Request('GET', Server::$url);
  669. $handler = new Handler\CurlHandler();
  670. $this->expectException(\InvalidArgumentException::class);
  671. $handler($req, ['on_headers' => 'error!']);
  672. }
  673. public function testRejectsPromiseWhenOnHeadersFails()
  674. {
  675. Server::flush();
  676. Server::enqueue([
  677. new Psr7\Response(200, ['X-Foo' => 'bar'], 'abc 123'),
  678. ]);
  679. $req = new Psr7\Request('GET', Server::$url);
  680. $handler = new Handler\CurlHandler();
  681. $promise = $handler($req, [
  682. 'on_headers' => static function () {
  683. throw new \Exception('test');
  684. },
  685. ]);
  686. $this->expectException(RequestException::class);
  687. $this->expectExceptionMessage('An error was encountered during the on_headers event');
  688. $promise->wait();
  689. }
  690. public function testSuccessfullyCallsOnHeadersBeforeWritingToSink()
  691. {
  692. Server::flush();
  693. Server::enqueue([
  694. new Psr7\Response(200, ['X-Foo' => 'bar'], 'abc 123'),
  695. ]);
  696. $req = new Psr7\Request('GET', Server::$url);
  697. $got = null;
  698. $stream = Psr7\Utils::streamFor();
  699. $stream = Psr7\FnStream::decorate($stream, [
  700. 'write' => static function ($data) use ($stream, &$got) {
  701. self::assertNotNull($got);
  702. return $stream->write($data);
  703. },
  704. ]);
  705. $handler = new Handler\CurlHandler();
  706. $promise = $handler($req, [
  707. 'sink' => $stream,
  708. 'on_headers' => static function (ResponseInterface $res) use (&$got) {
  709. $got = $res;
  710. self::assertEquals('bar', $res->getHeaderLine('X-Foo'));
  711. },
  712. ]);
  713. $response = $promise->wait();
  714. self::assertSame(200, $response->getStatusCode());
  715. self::assertSame('bar', $response->getHeaderLine('X-Foo'));
  716. self::assertSame('abc 123', (string) $response->getBody());
  717. }
  718. public function testInvokesOnStatsOnSuccess()
  719. {
  720. Server::flush();
  721. Server::enqueue([new Psr7\Response(200)]);
  722. $req = new Psr7\Request('GET', Server::$url);
  723. $gotStats = null;
  724. $handler = new Handler\CurlHandler();
  725. $promise = $handler($req, [
  726. 'on_stats' => static function (TransferStats $stats) use (&$gotStats) {
  727. $gotStats = $stats;
  728. },
  729. ]);
  730. $response = $promise->wait();
  731. self::assertSame(200, $response->getStatusCode());
  732. self::assertSame(200, $gotStats->getResponse()->getStatusCode());
  733. self::assertSame(
  734. Server::$url,
  735. (string) $gotStats->getEffectiveUri()
  736. );
  737. self::assertSame(
  738. Server::$url,
  739. (string) $gotStats->getRequest()->getUri()
  740. );
  741. self::assertGreaterThan(0, $gotStats->getTransferTime());
  742. self::assertArrayHasKey('appconnect_time', $gotStats->getHandlerStats());
  743. }
  744. public function testInvokesOnStatsOnError()
  745. {
  746. $req = new Psr7\Request('GET', 'http://127.0.0.1:123');
  747. $gotStats = null;
  748. $handler = new Handler\CurlHandler();
  749. $promise = $handler($req, [
  750. 'connect_timeout' => 0.001,
  751. 'timeout' => 0.001,
  752. 'on_stats' => static function (TransferStats $stats) use (&$gotStats) {
  753. $gotStats = $stats;
  754. },
  755. ]);
  756. $promise->wait(false);
  757. self::assertFalse($gotStats->hasResponse());
  758. self::assertSame(
  759. 'http://127.0.0.1:123',
  760. (string) $gotStats->getEffectiveUri()
  761. );
  762. self::assertSame(
  763. 'http://127.0.0.1:123',
  764. (string) $gotStats->getRequest()->getUri()
  765. );
  766. self::assertIsFloat($gotStats->getTransferTime());
  767. self::assertIsInt($gotStats->getHandlerErrorData());
  768. self::assertArrayHasKey('appconnect_time', $gotStats->getHandlerStats());
  769. }
  770. public function testRewindsBodyIfPossible()
  771. {
  772. $body = Psr7\Utils::streamFor(\str_repeat('x', 1024 * 1024 * 2));
  773. $body->seek(1024 * 1024);
  774. self::assertSame(1024 * 1024, $body->tell());
  775. $req = new Psr7\Request('POST', 'https://www.example.com', [
  776. 'Content-Length' => 1024 * 1024 * 2,
  777. ], $body);
  778. $factory = new CurlFactory(1);
  779. $factory->create($req, []);
  780. self::assertSame(0, $body->tell());
  781. }
  782. public function testDoesNotRewindUnseekableBody()
  783. {
  784. $body = Psr7\Utils::streamFor(\str_repeat('x', 1024 * 1024 * 2));
  785. $body->seek(1024 * 1024);
  786. $body = new Psr7\NoSeekStream($body);
  787. self::assertSame(1024 * 1024, $body->tell());
  788. $req = new Psr7\Request('POST', 'https://www.example.com', [
  789. 'Content-Length' => 1024 * 1024,
  790. ], $body);
  791. $factory = new CurlFactory(1);
  792. $factory->create($req, []);
  793. self::assertSame(1024 * 1024, $body->tell());
  794. }
  795. public function testRelease()
  796. {
  797. $factory = new CurlFactory(1);
  798. $easyHandle = new EasyHandle();
  799. $easyHandle->handle = \curl_init();
  800. self::assertEmpty($factory->release($easyHandle));
  801. }
  802. /**
  803. * https://github.com/guzzle/guzzle/issues/2735
  804. */
  805. public function testBodyEofOnWindows()
  806. {
  807. $expectedLength = 4097;
  808. Server::flush();
  809. Server::enqueue([
  810. new Psr7\Response(200, [
  811. 'Content-Length' => $expectedLength,
  812. ], \str_repeat('x', $expectedLength)),
  813. ]);
  814. $handler = new Handler\CurlMultiHandler();
  815. $request = new Psr7\Request('GET', Server::$url);
  816. $promise = $handler($request, []);
  817. $response = $promise->wait();
  818. $body = $response->getBody();
  819. $actualLength = 0;
  820. while (!$body->eof()) {
  821. $chunk = $body->read(4096);
  822. $actualLength += \strlen($chunk);
  823. }
  824. self::assertSame($expectedLength, $actualLength);
  825. }
  826. public function testHandlesGarbageHttpServerGracefully()
  827. {
  828. $a = new Handler\CurlMultiHandler();
  829. $this->expectException(RequestException::class);
  830. $this->expectExceptionMessage('cURL error 1: Received HTTP/0.9 when not allowed');
  831. $a(new Psr7\Request('GET', Server::$url.'guzzle-server/garbage'), [])->wait();
  832. }
  833. public function testHandlesInvalidStatusCodeGracefully()
  834. {
  835. $a = new Handler\CurlMultiHandler();
  836. $this->expectException(RequestException::class);
  837. $this->expectExceptionMessage('An error was encountered while creating the response');
  838. $a(new Psr7\Request('GET', Server::$url.'guzzle-server/bad-status'), [])->wait();
  839. }
  840. }