Client.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * Redistributions of files must retain the above copyright notice.
  8. *
  9. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  10. * @link http://cakephp.org CakePHP(tm) Project
  11. * @since 3.0.0
  12. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  13. */
  14. namespace Cake\Http;
  15. use Cake\Core\App;
  16. use Cake\Core\Exception\Exception;
  17. use Cake\Core\InstanceConfigTrait;
  18. use Cake\Http\Client\CookieCollection;
  19. use Cake\Http\Client\Request;
  20. use Cake\Utility\Hash;
  21. /**
  22. * The end user interface for doing HTTP requests.
  23. *
  24. * ### Scoped clients
  25. *
  26. * If you're doing multiple requests to the same hostname its often convenient
  27. * to use the constructor arguments to create a scoped client. This allows you
  28. * to keep your code DRY and not repeat hostnames, authentication, and other options.
  29. *
  30. * ### Doing requests
  31. *
  32. * Once you've created an instance of Client you can do requests
  33. * using several methods. Each corresponds to a different HTTP method.
  34. *
  35. * - get()
  36. * - post()
  37. * - put()
  38. * - delete()
  39. * - patch()
  40. *
  41. * ### Cookie management
  42. *
  43. * Client will maintain cookies from the responses done with
  44. * a client instance. These cookies will be automatically added
  45. * to future requests to matching hosts. Cookies will respect the
  46. * `Expires`, `Path` and `Domain` attributes. You can get the client's
  47. * CookieCollection using cookies()
  48. *
  49. * You can use the 'cookieJar' constructor option to provide a custom
  50. * cookie jar instance you've restored from cache/disk. By default
  51. * an empty instance of Cake\Http\Client\CookieCollection will be created.
  52. *
  53. * ### Sending request bodies
  54. *
  55. * By default any POST/PUT/PATCH/DELETE request with $data will
  56. * send their data as `application/x-www-form-urlencoded` unless
  57. * there are attached files. In that case `multipart/form-data`
  58. * will be used.
  59. *
  60. * When sending request bodies you can use the `type` option to
  61. * set the Content-Type for the request:
  62. *
  63. * ```
  64. * $http->get('/users', [], ['type' => 'json']);
  65. * ```
  66. *
  67. * The `type` option sets both the `Content-Type` and `Accept` header, to
  68. * the same mime type. When using `type` you can use either a full mime
  69. * type or an alias. If you need different types in the Accept and Content-Type
  70. * headers you should set them manually and not use `type`
  71. *
  72. * ### Using authentication
  73. *
  74. * By using the `auth` key you can use authentication. The type sub option
  75. * can be used to specify which authentication strategy you want to use.
  76. * CakePHP comes with a few built-in strategies:
  77. *
  78. * - Basic
  79. * - Digest
  80. * - Oauth
  81. *
  82. * ### Using proxies
  83. *
  84. * By using the `proxy` key you can set authentication credentials for
  85. * a proxy if you need to use one.. The type sub option can be used to
  86. * specify which authentication strategy you want to use.
  87. * CakePHP comes with built-in support for basic authentication.
  88. *
  89. */
  90. class Client
  91. {
  92. use InstanceConfigTrait;
  93. /**
  94. * Default configuration for the client.
  95. *
  96. * @var array
  97. */
  98. protected $_defaultConfig = [
  99. 'adapter' => 'Cake\Http\Client\Adapter\Stream',
  100. 'host' => null,
  101. 'port' => null,
  102. 'scheme' => 'http',
  103. 'timeout' => 30,
  104. 'ssl_verify_peer' => true,
  105. 'ssl_verify_peer_name' => true,
  106. 'ssl_verify_depth' => 5,
  107. 'ssl_verify_host' => true,
  108. 'redirect' => false,
  109. ];
  110. /**
  111. * List of cookies from responses made with this client.
  112. *
  113. * Cookies are indexed by the cookie's domain or
  114. * request host name.
  115. *
  116. * @var \Cake\Http\Client\CookieCollection
  117. */
  118. protected $_cookies;
  119. /**
  120. * Adapter for sending requests. Defaults to
  121. * Cake\Http\Client\Adapter\Stream
  122. *
  123. * @var \Cake\Http\Client\Adapter\Stream
  124. */
  125. protected $_adapter;
  126. /**
  127. * Create a new HTTP Client.
  128. *
  129. * ### Config options
  130. *
  131. * You can set the following options when creating a client:
  132. *
  133. * - host - The hostname to do requests on.
  134. * - port - The port to use.
  135. * - scheme - The default scheme/protocol to use. Defaults to http.
  136. * - timeout - The timeout in seconds. Defaults to 30
  137. * - ssl_verify_peer - Whether or not SSL certificates should be validated.
  138. * Defaults to true.
  139. * - ssl_verify_peer_name - Whether or not peer names should be validated.
  140. * Defaults to true.
  141. * - ssl_verify_depth - The maximum certificate chain depth to travers.
  142. * Defaults to 5.
  143. * - ssl_verify_host - Verify that the certificate and hostname match.
  144. * Defaults to true.
  145. * - redirect - Number of redirects to follow. Defaults to false.
  146. *
  147. * @param array $config Config options for scoped clients.
  148. */
  149. public function __construct($config = [])
  150. {
  151. $this->setConfig($config);
  152. $adapter = $this->_config['adapter'];
  153. $this->setConfig('adapter', null);
  154. if (is_string($adapter)) {
  155. $adapter = new $adapter();
  156. }
  157. $this->_adapter = $adapter;
  158. if (!empty($this->_config['cookieJar'])) {
  159. $this->_cookies = $this->_config['cookieJar'];
  160. $this->setConfig('cookieJar', null);
  161. } else {
  162. $this->_cookies = new CookieCollection();
  163. }
  164. }
  165. /**
  166. * Get the cookies stored in the Client.
  167. *
  168. * @return \Cake\Http\Client\CookieCollection
  169. */
  170. public function cookies()
  171. {
  172. return $this->_cookies;
  173. }
  174. /**
  175. * Do a GET request.
  176. *
  177. * The $data argument supports a special `_content` key
  178. * for providing a request body in a GET request. This is
  179. * generally not used but services like ElasticSearch use
  180. * this feature.
  181. *
  182. * @param string $url The url or path you want to request.
  183. * @param array $data The query data you want to send.
  184. * @param array $options Additional options for the request.
  185. * @return \Cake\Http\Client\Response
  186. */
  187. public function get($url, $data = [], array $options = [])
  188. {
  189. $options = $this->_mergeOptions($options);
  190. $body = null;
  191. if (isset($data['_content'])) {
  192. $body = $data['_content'];
  193. unset($data['_content']);
  194. }
  195. $url = $this->buildUrl($url, $data, $options);
  196. return $this->_doRequest(
  197. Request::METHOD_GET,
  198. $url,
  199. $body,
  200. $options
  201. );
  202. }
  203. /**
  204. * Do a POST request.
  205. *
  206. * @param string $url The url or path you want to request.
  207. * @param mixed $data The post data you want to send.
  208. * @param array $options Additional options for the request.
  209. * @return \Cake\Http\Client\Response
  210. */
  211. public function post($url, $data = [], array $options = [])
  212. {
  213. $options = $this->_mergeOptions($options);
  214. $url = $this->buildUrl($url, [], $options);
  215. return $this->_doRequest(Request::METHOD_POST, $url, $data, $options);
  216. }
  217. /**
  218. * Do a PUT request.
  219. *
  220. * @param string $url The url or path you want to request.
  221. * @param mixed $data The request data you want to send.
  222. * @param array $options Additional options for the request.
  223. * @return \Cake\Http\Client\Response
  224. */
  225. public function put($url, $data = [], array $options = [])
  226. {
  227. $options = $this->_mergeOptions($options);
  228. $url = $this->buildUrl($url, [], $options);
  229. return $this->_doRequest(Request::METHOD_PUT, $url, $data, $options);
  230. }
  231. /**
  232. * Do a PATCH request.
  233. *
  234. * @param string $url The url or path you want to request.
  235. * @param mixed $data The request data you want to send.
  236. * @param array $options Additional options for the request.
  237. * @return \Cake\Http\Client\Response
  238. */
  239. public function patch($url, $data = [], array $options = [])
  240. {
  241. $options = $this->_mergeOptions($options);
  242. $url = $this->buildUrl($url, [], $options);
  243. return $this->_doRequest(Request::METHOD_PATCH, $url, $data, $options);
  244. }
  245. /**
  246. * Do an OPTIONS request.
  247. *
  248. * @param string $url The url or path you want to request.
  249. * @param mixed $data The request data you want to send.
  250. * @param array $options Additional options for the request.
  251. * @return \Cake\Http\Client\Response
  252. */
  253. public function options($url, $data = [], array $options = [])
  254. {
  255. $options = $this->_mergeOptions($options);
  256. $url = $this->buildUrl($url, [], $options);
  257. return $this->_doRequest(Request::METHOD_OPTIONS, $url, $data, $options);
  258. }
  259. /**
  260. * Do a TRACE request.
  261. *
  262. * @param string $url The url or path you want to request.
  263. * @param mixed $data The request data you want to send.
  264. * @param array $options Additional options for the request.
  265. * @return \Cake\Http\Client\Response
  266. */
  267. public function trace($url, $data = [], array $options = [])
  268. {
  269. $options = $this->_mergeOptions($options);
  270. $url = $this->buildUrl($url, [], $options);
  271. return $this->_doRequest(Request::METHOD_TRACE, $url, $data, $options);
  272. }
  273. /**
  274. * Do a DELETE request.
  275. *
  276. * @param string $url The url or path you want to request.
  277. * @param mixed $data The request data you want to send.
  278. * @param array $options Additional options for the request.
  279. * @return \Cake\Http\Client\Response
  280. */
  281. public function delete($url, $data = [], array $options = [])
  282. {
  283. $options = $this->_mergeOptions($options);
  284. $url = $this->buildUrl($url, [], $options);
  285. return $this->_doRequest(Request::METHOD_DELETE, $url, $data, $options);
  286. }
  287. /**
  288. * Do a HEAD request.
  289. *
  290. * @param string $url The url or path you want to request.
  291. * @param array $data The query string data you want to send.
  292. * @param array $options Additional options for the request.
  293. * @return \Cake\Http\Client\Response
  294. */
  295. public function head($url, array $data = [], array $options = [])
  296. {
  297. $options = $this->_mergeOptions($options);
  298. $url = $this->buildUrl($url, $data, $options);
  299. return $this->_doRequest(Request::METHOD_HEAD, $url, '', $options);
  300. }
  301. /**
  302. * Helper method for doing non-GET requests.
  303. *
  304. * @param string $method HTTP method.
  305. * @param string $url URL to request.
  306. * @param mixed $data The request body.
  307. * @param array $options The options to use. Contains auth, proxy etc.
  308. * @return \Cake\Http\Client\Response
  309. */
  310. protected function _doRequest($method, $url, $data, $options)
  311. {
  312. $request = $this->_createRequest(
  313. $method,
  314. $url,
  315. $data,
  316. $options
  317. );
  318. return $this->send($request, $options);
  319. }
  320. /**
  321. * Does a recursive merge of the parameter with the scope config.
  322. *
  323. * @param array $options Options to merge.
  324. * @return array Options merged with set config.
  325. */
  326. protected function _mergeOptions($options)
  327. {
  328. return Hash::merge($this->_config, $options);
  329. }
  330. /**
  331. * Send a request.
  332. *
  333. * Used internally by other methods, but can also be used to send
  334. * handcrafted Request objects.
  335. *
  336. * @param \Cake\Http\Client\Request $request The request to send.
  337. * @param array $options Additional options to use.
  338. * @return \Cake\Http\Client\Response
  339. */
  340. public function send(Request $request, $options = [])
  341. {
  342. $responses = $this->_adapter->send($request, $options);
  343. $url = $request->getUri();
  344. foreach ($responses as $response) {
  345. $this->_cookies = $this->_cookies->addFromResponse($response, $request);
  346. }
  347. return array_pop($responses);
  348. }
  349. /**
  350. * Generate a URL based on the scoped client options.
  351. *
  352. * @param string $url Either a full URL or just the path.
  353. * @param string|array $query The query data for the URL.
  354. * @param array $options The config options stored with Client::config()
  355. * @return string A complete url with scheme, port, host, path.
  356. */
  357. public function buildUrl($url, $query = [], $options = [])
  358. {
  359. if (empty($options) && empty($query)) {
  360. return $url;
  361. }
  362. if ($query) {
  363. $q = (strpos($url, '?') === false) ? '?' : '&';
  364. $url .= $q;
  365. $url .= is_string($query) ? $query : http_build_query($query);
  366. }
  367. if (preg_match('#^https?://#', $url)) {
  368. return $url;
  369. }
  370. $defaults = [
  371. 'host' => null,
  372. 'port' => null,
  373. 'scheme' => 'http',
  374. ];
  375. $options += $defaults;
  376. $defaultPorts = [
  377. 'http' => 80,
  378. 'https' => 443
  379. ];
  380. $out = $options['scheme'] . '://' . $options['host'];
  381. if ($options['port'] && $options['port'] != $defaultPorts[$options['scheme']]) {
  382. $out .= ':' . $options['port'];
  383. }
  384. $out .= '/' . ltrim($url, '/');
  385. return $out;
  386. }
  387. /**
  388. * Creates a new request object based on the parameters.
  389. *
  390. * @param string $method HTTP method name.
  391. * @param string $url The url including query string.
  392. * @param mixed $data The request body.
  393. * @param array $options The options to use. Contains auth, proxy etc.
  394. * @return \Cake\Http\Client\Request
  395. */
  396. protected function _createRequest($method, $url, $data, $options)
  397. {
  398. $headers = isset($options['headers']) ? (array)$options['headers'] : [];
  399. if (isset($options['type'])) {
  400. $headers = array_merge($headers, $this->_typeHeaders($options['type']));
  401. }
  402. if (is_string($data) && !isset($headers['Content-Type']) && !isset($headers['content-type'])) {
  403. $headers['Content-Type'] = 'application/x-www-form-urlencoded';
  404. }
  405. $request = new Request($url, $method, $headers, $data);
  406. $cookies = isset($options['cookies']) ? $options['cookies'] : [];
  407. $request = $this->_cookies->addToRequest($request, $cookies);
  408. if (isset($options['auth'])) {
  409. $request = $this->_addAuthentication($request, $options);
  410. }
  411. if (isset($options['proxy'])) {
  412. $request = $this->_addProxy($request, $options);
  413. }
  414. return $request;
  415. }
  416. /**
  417. * Returns headers for Accept/Content-Type based on a short type
  418. * or full mime-type.
  419. *
  420. * @param string $type short type alias or full mimetype.
  421. * @return array Headers to set on the request.
  422. * @throws \Cake\Core\Exception\Exception When an unknown type alias is used.
  423. */
  424. protected function _typeHeaders($type)
  425. {
  426. if (strpos($type, '/') !== false) {
  427. return [
  428. 'Accept' => $type,
  429. 'Content-Type' => $type
  430. ];
  431. }
  432. $typeMap = [
  433. 'json' => 'application/json',
  434. 'xml' => 'application/xml',
  435. ];
  436. if (!isset($typeMap[$type])) {
  437. throw new Exception("Unknown type alias '$type'.");
  438. }
  439. return [
  440. 'Accept' => $typeMap[$type],
  441. 'Content-Type' => $typeMap[$type],
  442. ];
  443. }
  444. /**
  445. * Add authentication headers to the request.
  446. *
  447. * Uses the authentication type to choose the correct strategy
  448. * and use its methods to add headers.
  449. *
  450. * @param \Cake\Http\Client\Request $request The request to modify.
  451. * @param array $options Array of options containing the 'auth' key.
  452. * @return \Cake\Http\Client\Request The updated request object.
  453. */
  454. protected function _addAuthentication(Request $request, $options)
  455. {
  456. $auth = $options['auth'];
  457. $adapter = $this->_createAuth($auth, $options);
  458. $result = $adapter->authentication($request, $options['auth']);
  459. return $result ?: $request;
  460. }
  461. /**
  462. * Add proxy authentication headers.
  463. *
  464. * Uses the authentication type to choose the correct strategy
  465. * and use its methods to add headers.
  466. *
  467. * @param \Cake\Http\Client\Request $request The request to modify.
  468. * @param array $options Array of options containing the 'proxy' key.
  469. * @return \Cake\Http\Client\Request The updated request object.
  470. */
  471. protected function _addProxy(Request $request, $options)
  472. {
  473. $auth = $options['proxy'];
  474. $adapter = $this->_createAuth($auth, $options);
  475. $result = $adapter->proxyAuthentication($request, $options['proxy']);
  476. return $result ?: $request;
  477. }
  478. /**
  479. * Create the authentication strategy.
  480. *
  481. * Use the configuration options to create the correct
  482. * authentication strategy handler.
  483. *
  484. * @param array $auth The authentication options to use.
  485. * @param array $options The overall request options to use.
  486. * @return mixed Authentication strategy instance.
  487. * @throws \Cake\Core\Exception\Exception when an invalid strategy is chosen.
  488. */
  489. protected function _createAuth($auth, $options)
  490. {
  491. if (empty($auth['type'])) {
  492. $auth['type'] = 'basic';
  493. }
  494. $name = ucfirst($auth['type']);
  495. $class = App::className($name, 'Http/Client/Auth');
  496. if (!$class) {
  497. throw new Exception(
  498. sprintf('Invalid authentication type %s', $name)
  499. );
  500. }
  501. return new $class($this, $options);
  502. }
  503. }
  504. // @deprecated Backwards compatibility with earler 3.x versions.
  505. class_alias('Cake\Http\Client', 'Cake\Network\Http\Client');