StreamHandlerTest.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. <?php
  2. namespace GuzzleHttp\Tests\Ring\Client;
  3. use GuzzleHttp\Ring\Client\ClientUtils;
  4. use GuzzleHttp\Ring\Core;
  5. use GuzzleHttp\Ring\Client\StreamHandler;
  6. class StreamHandlerTest extends \PHPUnit_Framework_TestCase
  7. {
  8. public function testReturnsResponseForSuccessfulRequest()
  9. {
  10. $this->queueRes();
  11. $handler = new StreamHandler();
  12. $response = $handler([
  13. 'http_method' => 'GET',
  14. 'uri' => '/',
  15. 'headers' => [
  16. 'host' => [Server::$host],
  17. 'Foo' => ['Bar'],
  18. ],
  19. ]);
  20. $this->assertEquals('1.1', $response['version']);
  21. $this->assertEquals(200, $response['status']);
  22. $this->assertEquals('OK', $response['reason']);
  23. $this->assertEquals(['Bar'], $response['headers']['Foo']);
  24. $this->assertEquals(['8'], $response['headers']['Content-Length']);
  25. $this->assertEquals('hi there', Core::body($response));
  26. $sent = Server::received()[0];
  27. $this->assertEquals('GET', $sent['http_method']);
  28. $this->assertEquals('/', $sent['resource']);
  29. $this->assertEquals(['127.0.0.1:8125'], $sent['headers']['host']);
  30. $this->assertEquals('Bar', Core::header($sent, 'foo'));
  31. }
  32. public function testAddsErrorToResponse()
  33. {
  34. $handler = new StreamHandler();
  35. $result = $handler([
  36. 'http_method' => 'GET',
  37. 'headers' => ['host' => ['localhost:123']],
  38. 'client' => ['timeout' => 0.01],
  39. ]);
  40. $this->assertInstanceOf(
  41. 'GuzzleHttp\Ring\Future\CompletedFutureArray',
  42. $result
  43. );
  44. $this->assertNull($result['status']);
  45. $this->assertNull($result['body']);
  46. $this->assertEquals([], $result['headers']);
  47. $this->assertInstanceOf(
  48. 'GuzzleHttp\Ring\Exception\RingException',
  49. $result['error']
  50. );
  51. }
  52. public function testEnsuresTheHttpProtocol()
  53. {
  54. $handler = new StreamHandler();
  55. $result = $handler([
  56. 'http_method' => 'GET',
  57. 'url' => 'ftp://localhost:123',
  58. ]);
  59. $this->assertArrayHasKey('error', $result);
  60. $this->assertContains(
  61. 'URL is invalid: ftp://localhost:123',
  62. $result['error']->getMessage()
  63. );
  64. }
  65. public function testStreamAttributeKeepsStreamOpen()
  66. {
  67. $this->queueRes();
  68. $handler = new StreamHandler();
  69. $response = $handler([
  70. 'http_method' => 'PUT',
  71. 'uri' => '/foo',
  72. 'query_string' => 'baz=bar',
  73. 'headers' => [
  74. 'host' => [Server::$host],
  75. 'Foo' => ['Bar'],
  76. ],
  77. 'body' => 'test',
  78. 'client' => ['stream' => true],
  79. ]);
  80. $this->assertEquals(200, $response['status']);
  81. $this->assertEquals('OK', $response['reason']);
  82. $this->assertEquals('8', Core::header($response, 'Content-Length'));
  83. $body = $response['body'];
  84. $this->assertTrue(is_resource($body));
  85. $this->assertEquals('http', stream_get_meta_data($body)['wrapper_type']);
  86. $this->assertEquals('hi there', stream_get_contents($body));
  87. fclose($body);
  88. $sent = Server::received()[0];
  89. $this->assertEquals('PUT', $sent['http_method']);
  90. $this->assertEquals('/foo', $sent['uri']);
  91. $this->assertEquals('baz=bar', $sent['query_string']);
  92. $this->assertEquals('/foo?baz=bar', $sent['resource']);
  93. $this->assertEquals('127.0.0.1:8125', Core::header($sent, 'host'));
  94. $this->assertEquals('Bar', Core::header($sent, 'foo'));
  95. }
  96. public function testDrainsResponseIntoTempStream()
  97. {
  98. $this->queueRes();
  99. $handler = new StreamHandler();
  100. $response = $handler([
  101. 'http_method' => 'GET',
  102. 'uri' => '/',
  103. 'headers' => ['host' => [Server::$host]],
  104. ]);
  105. $body = $response['body'];
  106. $this->assertEquals('php://temp', stream_get_meta_data($body)['uri']);
  107. $this->assertEquals('hi', fread($body, 2));
  108. fclose($body);
  109. }
  110. public function testDrainsResponseIntoSaveToBody()
  111. {
  112. $r = fopen('php://temp', 'r+');
  113. $this->queueRes();
  114. $handler = new StreamHandler();
  115. $response = $handler([
  116. 'http_method' => 'GET',
  117. 'uri' => '/',
  118. 'headers' => ['host' => [Server::$host]],
  119. 'client' => ['save_to' => $r],
  120. ]);
  121. $body = $response['body'];
  122. $this->assertEquals('php://temp', stream_get_meta_data($body)['uri']);
  123. $this->assertEquals('hi', fread($body, 2));
  124. $this->assertEquals(' there', stream_get_contents($r));
  125. fclose($r);
  126. }
  127. public function testDrainsResponseIntoSaveToBodyAtPath()
  128. {
  129. $tmpfname = tempnam('/tmp', 'save_to_path');
  130. $this->queueRes();
  131. $handler = new StreamHandler();
  132. $response = $handler([
  133. 'http_method' => 'GET',
  134. 'uri' => '/',
  135. 'headers' => ['host' => [Server::$host]],
  136. 'client' => ['save_to' => $tmpfname],
  137. ]);
  138. $body = $response['body'];
  139. $this->assertInstanceOf('GuzzleHttp\Stream\StreamInterface', $body);
  140. $this->assertEquals($tmpfname, $body->getMetadata('uri'));
  141. $this->assertEquals('hi', $body->read(2));
  142. $body->close();
  143. unlink($tmpfname);
  144. }
  145. public function testAutomaticallyDecompressGzip()
  146. {
  147. Server::flush();
  148. $content = gzencode('test');
  149. Server::enqueue([
  150. [
  151. 'status' => 200,
  152. 'reason' => 'OK',
  153. 'headers' => [
  154. 'Content-Encoding' => ['gzip'],
  155. 'Content-Length' => [strlen($content)],
  156. ],
  157. 'body' => $content,
  158. ],
  159. ]);
  160. $handler = new StreamHandler();
  161. $response = $handler([
  162. 'http_method' => 'GET',
  163. 'headers' => ['host' => [Server::$host]],
  164. 'uri' => '/',
  165. 'client' => ['decode_content' => true],
  166. ]);
  167. $this->assertEquals('test', Core::body($response));
  168. }
  169. public function testDoesNotForceGzipDecode()
  170. {
  171. Server::flush();
  172. $content = gzencode('test');
  173. Server::enqueue([
  174. [
  175. 'status' => 200,
  176. 'reason' => 'OK',
  177. 'headers' => [
  178. 'Content-Encoding' => ['gzip'],
  179. 'Content-Length' => [strlen($content)],
  180. ],
  181. 'body' => $content,
  182. ],
  183. ]);
  184. $handler = new StreamHandler();
  185. $response = $handler([
  186. 'http_method' => 'GET',
  187. 'headers' => ['host' => [Server::$host]],
  188. 'uri' => '/',
  189. 'client' => ['stream' => true, 'decode_content' => false],
  190. ]);
  191. $this->assertSame($content, Core::body($response));
  192. }
  193. public function testProtocolVersion()
  194. {
  195. $this->queueRes();
  196. $handler = new StreamHandler();
  197. $handler([
  198. 'http_method' => 'GET',
  199. 'uri' => '/',
  200. 'headers' => ['host' => [Server::$host]],
  201. 'version' => 1.0,
  202. ]);
  203. $this->assertEquals(1.0, Server::received()[0]['version']);
  204. }
  205. protected function getSendResult(array $opts)
  206. {
  207. $this->queueRes();
  208. $handler = new StreamHandler();
  209. $opts['stream'] = true;
  210. return $handler([
  211. 'http_method' => 'GET',
  212. 'uri' => '/',
  213. 'headers' => ['host' => [Server::$host]],
  214. 'client' => $opts,
  215. ]);
  216. }
  217. public function testAddsProxy()
  218. {
  219. $res = $this->getSendResult(['stream' => true, 'proxy' => '127.0.0.1:8125']);
  220. $opts = stream_context_get_options($res['body']);
  221. $this->assertEquals('127.0.0.1:8125', $opts['http']['proxy']);
  222. }
  223. public function testAddsTimeout()
  224. {
  225. $res = $this->getSendResult(['stream' => true, 'timeout' => 200]);
  226. $opts = stream_context_get_options($res['body']);
  227. $this->assertEquals(200, $opts['http']['timeout']);
  228. }
  229. public function testVerifiesVerifyIsValidIfPath()
  230. {
  231. $res = $this->getSendResult(['verify' => '/does/not/exist']);
  232. $this->assertContains(
  233. 'SSL CA bundle not found: /does/not/exist',
  234. (string) $res['error']
  235. );
  236. }
  237. public function testVerifyCanBeDisabled()
  238. {
  239. $res = $this->getSendResult(['verify' => false]);
  240. $this->assertArrayNotHasKey('error', $res);
  241. }
  242. public function testVerifiesCertIfValidPath()
  243. {
  244. $res = $this->getSendResult(['cert' => '/does/not/exist']);
  245. $this->assertContains(
  246. 'SSL certificate not found: /does/not/exist',
  247. (string) $res['error']
  248. );
  249. }
  250. public function testVerifyCanBeSetToPath()
  251. {
  252. $path = $path = ClientUtils::getDefaultCaBundle();
  253. $res = $this->getSendResult(['verify' => $path]);
  254. $this->assertArrayNotHasKey('error', $res);
  255. $opts = stream_context_get_options($res['body']);
  256. $this->assertEquals(true, $opts['ssl']['verify_peer']);
  257. $this->assertEquals($path, $opts['ssl']['cafile']);
  258. $this->assertTrue(file_exists($opts['ssl']['cafile']));
  259. }
  260. public function testUsesSystemDefaultBundle()
  261. {
  262. $path = $path = ClientUtils::getDefaultCaBundle();
  263. $res = $this->getSendResult(['verify' => true]);
  264. $this->assertArrayNotHasKey('error', $res);
  265. $opts = stream_context_get_options($res['body']);
  266. if (PHP_VERSION_ID < 50600) {
  267. $this->assertEquals($path, $opts['ssl']['cafile']);
  268. }
  269. }
  270. public function testEnsuresVerifyOptionIsValid()
  271. {
  272. $res = $this->getSendResult(['verify' => 10]);
  273. $this->assertContains(
  274. 'Invalid verify request option',
  275. (string) $res['error']
  276. );
  277. }
  278. public function testCanSetPasswordWhenSettingCert()
  279. {
  280. $path = __FILE__;
  281. $res = $this->getSendResult(['cert' => [$path, 'foo']]);
  282. $opts = stream_context_get_options($res['body']);
  283. $this->assertEquals($path, $opts['ssl']['local_cert']);
  284. $this->assertEquals('foo', $opts['ssl']['passphrase']);
  285. }
  286. public function testDebugAttributeWritesToStream()
  287. {
  288. $this->queueRes();
  289. $f = fopen('php://temp', 'w+');
  290. $this->getSendResult(['debug' => $f]);
  291. fseek($f, 0);
  292. $contents = stream_get_contents($f);
  293. $this->assertContains('<GET http://127.0.0.1:8125/> [CONNECT]', $contents);
  294. $this->assertContains('<GET http://127.0.0.1:8125/> [FILE_SIZE_IS]', $contents);
  295. $this->assertContains('<GET http://127.0.0.1:8125/> [PROGRESS]', $contents);
  296. }
  297. public function testDebugAttributeWritesStreamInfoToBuffer()
  298. {
  299. $called = false;
  300. $this->queueRes();
  301. $buffer = fopen('php://temp', 'r+');
  302. $this->getSendResult([
  303. 'progress' => function () use (&$called) { $called = true; },
  304. 'debug' => $buffer,
  305. ]);
  306. fseek($buffer, 0);
  307. $contents = stream_get_contents($buffer);
  308. $this->assertContains('<GET http://127.0.0.1:8125/> [CONNECT]', $contents);
  309. $this->assertContains('<GET http://127.0.0.1:8125/> [FILE_SIZE_IS] message: "Content-Length: 8"', $contents);
  310. $this->assertContains('<GET http://127.0.0.1:8125/> [PROGRESS] bytes_max: "8"', $contents);
  311. $this->assertTrue($called);
  312. }
  313. public function testEmitsProgressInformation()
  314. {
  315. $called = [];
  316. $this->queueRes();
  317. $this->getSendResult([
  318. 'progress' => function () use (&$called) {
  319. $called[] = func_get_args();
  320. },
  321. ]);
  322. $this->assertNotEmpty($called);
  323. $this->assertEquals(8, $called[0][0]);
  324. $this->assertEquals(0, $called[0][1]);
  325. }
  326. public function testEmitsProgressInformationAndDebugInformation()
  327. {
  328. $called = [];
  329. $this->queueRes();
  330. $buffer = fopen('php://memory', 'w+');
  331. $this->getSendResult([
  332. 'debug' => $buffer,
  333. 'progress' => function () use (&$called) {
  334. $called[] = func_get_args();
  335. },
  336. ]);
  337. $this->assertNotEmpty($called);
  338. $this->assertEquals(8, $called[0][0]);
  339. $this->assertEquals(0, $called[0][1]);
  340. rewind($buffer);
  341. $this->assertNotEmpty(stream_get_contents($buffer));
  342. fclose($buffer);
  343. }
  344. public function testAddsProxyByProtocol()
  345. {
  346. $url = str_replace('http', 'tcp', Server::$url);
  347. $res = $this->getSendResult(['proxy' => ['http' => $url]]);
  348. $opts = stream_context_get_options($res['body']);
  349. $this->assertEquals($url, $opts['http']['proxy']);
  350. }
  351. public function testPerformsShallowMergeOfCustomContextOptions()
  352. {
  353. $res = $this->getSendResult([
  354. 'stream_context' => [
  355. 'http' => [
  356. 'request_fulluri' => true,
  357. 'method' => 'HEAD',
  358. ],
  359. 'socket' => [
  360. 'bindto' => '127.0.0.1:0',
  361. ],
  362. 'ssl' => [
  363. 'verify_peer' => false,
  364. ],
  365. ],
  366. ]);
  367. $opts = stream_context_get_options($res['body']);
  368. $this->assertEquals('HEAD', $opts['http']['method']);
  369. $this->assertTrue($opts['http']['request_fulluri']);
  370. $this->assertFalse($opts['ssl']['verify_peer']);
  371. $this->assertEquals('127.0.0.1:0', $opts['socket']['bindto']);
  372. }
  373. public function testEnsuresThatStreamContextIsAnArray()
  374. {
  375. $res = $this->getSendResult(['stream_context' => 'foo']);
  376. $this->assertContains(
  377. 'stream_context must be an array',
  378. (string) $res['error']
  379. );
  380. }
  381. public function testDoesNotAddContentTypeByDefault()
  382. {
  383. $this->queueRes();
  384. $handler = new StreamHandler();
  385. $handler([
  386. 'http_method' => 'PUT',
  387. 'uri' => '/',
  388. 'headers' => ['host' => [Server::$host], 'content-length' => [3]],
  389. 'body' => 'foo',
  390. ]);
  391. $req = Server::received()[0];
  392. $this->assertEquals('', Core::header($req, 'Content-Type'));
  393. $this->assertEquals(3, Core::header($req, 'Content-Length'));
  394. }
  395. private function queueRes()
  396. {
  397. Server::flush();
  398. Server::enqueue([
  399. [
  400. 'status' => 200,
  401. 'reason' => 'OK',
  402. 'headers' => [
  403. 'Foo' => ['Bar'],
  404. 'Content-Length' => [8],
  405. ],
  406. 'body' => 'hi there',
  407. ],
  408. ]);
  409. }
  410. public function testSupports100Continue()
  411. {
  412. Server::flush();
  413. Server::enqueue([
  414. [
  415. 'status' => '200',
  416. 'reason' => 'OK',
  417. 'headers' => [
  418. 'Test' => ['Hello'],
  419. 'Content-Length' => ['4'],
  420. ],
  421. 'body' => 'test',
  422. ],
  423. ]);
  424. $request = [
  425. 'http_method' => 'PUT',
  426. 'headers' => [
  427. 'Host' => [Server::$host],
  428. 'Expect' => ['100-Continue'],
  429. ],
  430. 'body' => 'test',
  431. ];
  432. $handler = new StreamHandler();
  433. $response = $handler($request);
  434. $this->assertEquals(200, $response['status']);
  435. $this->assertEquals('OK', $response['reason']);
  436. $this->assertEquals(['Hello'], $response['headers']['Test']);
  437. $this->assertEquals(['4'], $response['headers']['Content-Length']);
  438. $this->assertEquals('test', Core::body($response));
  439. }
  440. }