CurlFactoryTest.php 27 KB

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