Client.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\httpclient;
  8. use yii\base\Component;
  9. use Yii;
  10. use yii\base\InvalidParamException;
  11. use yii\helpers\StringHelper;
  12. /**
  13. * Client provide high level interface for HTTP requests execution.
  14. *
  15. * @property Transport $transport HTTP message transport instance. Note that the type of this property differs
  16. * in getter and setter. See [[getTransport()]] and [[setTransport()]] for details.
  17. *
  18. * @author Paul Klimov <klimov.paul@gmail.com>
  19. * @since 2.0
  20. */
  21. class Client extends Component
  22. {
  23. /**
  24. * @event RequestEvent an event raised right before sending request.
  25. */
  26. const EVENT_BEFORE_SEND = 'beforeSend';
  27. /**
  28. * @event RequestEvent an event raised right after request has been sent.
  29. */
  30. const EVENT_AFTER_SEND = 'afterSend';
  31. /**
  32. * JSON format
  33. */
  34. const FORMAT_JSON = 'json';
  35. /**
  36. * urlencoded by RFC1738 query string, like name1=value1&name2=value2
  37. * @see http://php.net/manual/en/function.urlencode.php
  38. */
  39. const FORMAT_URLENCODED = 'urlencoded';
  40. /**
  41. * urlencoded by PHP_QUERY_RFC3986 query string, like name1=value1&name2=value2
  42. * @see http://php.net/manual/en/function.rawurlencode.php
  43. */
  44. const FORMAT_RAW_URLENCODED = 'raw-urlencoded';
  45. /**
  46. * XML format
  47. */
  48. const FORMAT_XML = 'xml';
  49. /**
  50. * @var string base request URL.
  51. */
  52. public $baseUrl;
  53. /**
  54. * @var array the formatters for converting data into the content of the specified [[format]].
  55. * The array keys are the format names, and the array values are the corresponding configurations
  56. * for creating the formatter objects.
  57. */
  58. public $formatters = [];
  59. /**
  60. * @var array the parsers for converting content of the specified [[format]] into the data.
  61. * The array keys are the format names, and the array values are the corresponding configurations
  62. * for creating the parser objects.
  63. */
  64. public $parsers = [];
  65. /**
  66. * @var array request object configuration.
  67. */
  68. public $requestConfig = [];
  69. /**
  70. * @var array response config configuration.
  71. */
  72. public $responseConfig = [];
  73. /**
  74. * @var int maximum symbols count of the request content, which should be taken to compose a
  75. * log and profile messages. Exceeding content will be truncated.
  76. * @see createRequestLogToken()
  77. */
  78. public $contentLoggingMaxSize = 2000;
  79. /**
  80. * @var Transport|array|string|callable HTTP message transport.
  81. */
  82. private $_transport = 'yii\httpclient\StreamTransport';
  83. /**
  84. * Sets the HTTP message transport. It can be specified in one of the following forms:
  85. *
  86. * - an instance of `Transport`: actual transport object to be used
  87. * - a string: representing the class name of the object to be created
  88. * - a configuration array: the array must contain a `class` element which is treated as the object class,
  89. * and the rest of the name-value pairs will be used to initialize the corresponding object properties
  90. * - a PHP callable: either an anonymous function or an array representing a class method (`[$class or $object, $method]`).
  91. * The callable should return a new instance of the object being created.
  92. * @param Transport|array|string $transport HTTP message transport
  93. */
  94. public function setTransport($transport)
  95. {
  96. $this->_transport = $transport;
  97. }
  98. /**
  99. * @return Transport HTTP message transport instance.
  100. */
  101. public function getTransport()
  102. {
  103. if (!is_object($this->_transport)) {
  104. $this->_transport = Yii::createObject($this->_transport);
  105. }
  106. return $this->_transport;
  107. }
  108. /**
  109. * Returns HTTP message formatter instance for the specified format.
  110. * @param string $format format name.
  111. * @return FormatterInterface formatter instance.
  112. * @throws InvalidParamException on invalid format name.
  113. */
  114. public function getFormatter($format)
  115. {
  116. static $defaultFormatters = [
  117. self::FORMAT_JSON => 'yii\httpclient\JsonFormatter',
  118. self::FORMAT_URLENCODED => [
  119. 'class' => 'yii\httpclient\UrlEncodedFormatter',
  120. 'encodingType' => PHP_QUERY_RFC1738
  121. ],
  122. self::FORMAT_RAW_URLENCODED => [
  123. 'class' => 'yii\httpclient\UrlEncodedFormatter',
  124. 'encodingType' => PHP_QUERY_RFC3986
  125. ],
  126. self::FORMAT_XML => 'yii\httpclient\XmlFormatter',
  127. ];
  128. if (!isset($this->formatters[$format])) {
  129. if (!isset($defaultFormatters[$format])) {
  130. throw new InvalidParamException("Unrecognized format '{$format}'");
  131. }
  132. $this->formatters[$format] = $defaultFormatters[$format];
  133. }
  134. if (!is_object($this->formatters[$format])) {
  135. $this->formatters[$format] = Yii::createObject($this->formatters[$format]);
  136. }
  137. return $this->formatters[$format];
  138. }
  139. /**
  140. * Returns HTTP message parser instance for the specified format.
  141. * @param string $format format name
  142. * @return ParserInterface parser instance.
  143. * @throws InvalidParamException on invalid format name.
  144. */
  145. public function getParser($format)
  146. {
  147. static $defaultParsers = [
  148. self::FORMAT_JSON => 'yii\httpclient\JsonParser',
  149. self::FORMAT_URLENCODED => 'yii\httpclient\UrlEncodedParser',
  150. self::FORMAT_RAW_URLENCODED => 'yii\httpclient\UrlEncodedParser',
  151. self::FORMAT_XML => 'yii\httpclient\XmlParser',
  152. ];
  153. if (!isset($this->parsers[$format])) {
  154. if (!isset($defaultParsers[$format])) {
  155. throw new InvalidParamException("Unrecognized format '{$format}'");
  156. }
  157. $this->parsers[$format] = $defaultParsers[$format];
  158. }
  159. if (!is_object($this->parsers[$format])) {
  160. $this->parsers[$format] = Yii::createObject($this->parsers[$format]);
  161. }
  162. return $this->parsers[$format];
  163. }
  164. /**
  165. * @return Request request instance.
  166. */
  167. public function createRequest()
  168. {
  169. $config = $this->requestConfig;
  170. if (!isset($config['class'])) {
  171. $config['class'] = Request::className();
  172. }
  173. $config['client'] = $this;
  174. return Yii::createObject($config);
  175. }
  176. /**
  177. * Creates a response instance.
  178. * @param string $content raw content
  179. * @param array $headers headers list.
  180. * @return Response request instance.
  181. */
  182. public function createResponse($content = null, array $headers = [])
  183. {
  184. $config = $this->responseConfig;
  185. if (!isset($config['class'])) {
  186. $config['class'] = Response::className();
  187. }
  188. $config['client'] = $this;
  189. $response = Yii::createObject($config);
  190. $response->setContent($content);
  191. $response->setHeaders($headers);
  192. return $response;
  193. }
  194. /**
  195. * Performs given request.
  196. * @param Request $request request to be sent.
  197. * @return Response response instance.
  198. * @throws Exception on failure.
  199. */
  200. public function send($request)
  201. {
  202. return $this->getTransport()->send($request);
  203. }
  204. /**
  205. * Performs multiple HTTP requests in parallel.
  206. * This method accepts an array of the [[Request]] objects and returns an array of the [[Response]] objects.
  207. * Keys of the response array correspond the ones from request array.
  208. *
  209. * ```php
  210. * $client = new Client();
  211. * $requests = [
  212. * 'news' => $client->get('http://domain.com/news'),
  213. * 'friends' => $client->get('http://domain.com/user/friends', ['userId' => 12]),
  214. * ];
  215. * $responses = $client->batchSend($requests);
  216. * var_dump($responses['news']->isOk);
  217. * var_dump($responses['friends']->isOk);
  218. * ```
  219. *
  220. * @param Request[] $requests requests to perform.
  221. * @return Response[] responses list.
  222. */
  223. public function batchSend(array $requests)
  224. {
  225. return $this->getTransport()->batchSend($requests);
  226. }
  227. /**
  228. * Composes the log/profiling message token for the given HTTP request parameters.
  229. * This method should be used by transports during request sending logging.
  230. * @param string $method request method name.
  231. * @param string $url request URL.
  232. * @param array $headers request headers.
  233. * @param string $content request content.
  234. * @return string log token.
  235. */
  236. public function createRequestLogToken($method, $url, $headers, $content)
  237. {
  238. $token = strtoupper($method) . ' ' . $url;
  239. if (!empty($headers)) {
  240. $token .= "\n" . implode("\n", $headers);
  241. }
  242. if ($content !== null) {
  243. $token .= "\n\n" . StringHelper::truncate($content, $this->contentLoggingMaxSize);
  244. }
  245. return $token;
  246. }
  247. // Create request shortcut methods :
  248. /**
  249. * Creates 'GET' request.
  250. * @param string $url target URL.
  251. * @param array|string $data if array - request data, otherwise - request content.
  252. * @param array $headers request headers.
  253. * @param array $options request options.
  254. * @return Request request instance.
  255. */
  256. public function get($url, $data = null, $headers = [], $options = [])
  257. {
  258. return $this->createRequestShortcut('get', $url, $data, $headers, $options);
  259. }
  260. /**
  261. * Creates 'POST' request.
  262. * @param string $url target URL.
  263. * @param array|string $data if array - request data, otherwise - request content.
  264. * @param array $headers request headers.
  265. * @param array $options request options.
  266. * @return Request request instance.
  267. */
  268. public function post($url, $data = null, $headers = [], $options = [])
  269. {
  270. return $this->createRequestShortcut('post', $url, $data, $headers, $options);
  271. }
  272. /**
  273. * Creates 'PUT' request.
  274. * @param string $url target URL.
  275. * @param array|string $data if array - request data, otherwise - request content.
  276. * @param array $headers request headers.
  277. * @param array $options request options.
  278. * @return Request request instance.
  279. */
  280. public function put($url, $data = null, $headers = [], $options = [])
  281. {
  282. return $this->createRequestShortcut('put', $url, $data, $headers, $options);
  283. }
  284. /**
  285. * Creates 'PATCH' request.
  286. * @param string $url target URL.
  287. * @param array|string $data if array - request data, otherwise - request content.
  288. * @param array $headers request headers.
  289. * @param array $options request options.
  290. * @return Request request instance.
  291. */
  292. public function patch($url, $data = null, $headers = [], $options = [])
  293. {
  294. return $this->createRequestShortcut('patch', $url, $data, $headers, $options);
  295. }
  296. /**
  297. * Creates 'DELETE' request.
  298. * @param string $url target URL.
  299. * @param array|string $data if array - request data, otherwise - request content.
  300. * @param array $headers request headers.
  301. * @param array $options request options.
  302. * @return Request request instance.
  303. */
  304. public function delete($url, $data = null, $headers = [], $options = [])
  305. {
  306. return $this->createRequestShortcut('delete', $url, $data, $headers, $options);
  307. }
  308. /**
  309. * Creates 'HEAD' request.
  310. * @param string $url target URL.
  311. * @param array $headers request headers.
  312. * @param array $options request options.
  313. * @return Request request instance.
  314. */
  315. public function head($url, $headers = [], $options = [])
  316. {
  317. return $this->createRequestShortcut('head', $url, null, $headers, $options);
  318. }
  319. /**
  320. * Creates 'OPTIONS' request.
  321. * @param string $url target URL.
  322. * @param array $options request options.
  323. * @return Request request instance.
  324. */
  325. public function options($url, $options = [])
  326. {
  327. return $this->createRequestShortcut('options', $url, null, [], $options);
  328. }
  329. /**
  330. * This method is invoked right before request is sent.
  331. * The method will trigger the [[EVENT_BEFORE_SEND]] event.
  332. * @param Request $request request instance.
  333. * @since 2.0.1
  334. */
  335. public function beforeSend($request)
  336. {
  337. $event = new RequestEvent();
  338. $event->request = $request;
  339. $this->trigger(self::EVENT_BEFORE_SEND, $event);
  340. }
  341. /**
  342. * This method is invoked right after request is sent.
  343. * The method will trigger the [[EVENT_AFTER_SEND]] event.
  344. * @param Request $request request instance.
  345. * @param Response $response received response instance.
  346. * @since 2.0.1
  347. */
  348. public function afterSend($request, $response)
  349. {
  350. $event = new RequestEvent();
  351. $event->request = $request;
  352. $event->response = $response;
  353. $this->trigger(self::EVENT_AFTER_SEND, $event);
  354. }
  355. /**
  356. * @param string $method
  357. * @param string $url
  358. * @param array|string $data
  359. * @param array $headers
  360. * @param array $options
  361. * @return Request request instance.
  362. */
  363. private function createRequestShortcut($method, $url, $data, $headers, $options)
  364. {
  365. $request = $this->createRequest()
  366. ->setMethod($method)
  367. ->setUrl($url)
  368. ->addHeaders($headers)
  369. ->addOptions($options);
  370. if (is_array($data)) {
  371. $request->setData($data);
  372. } else {
  373. $request->setContent($data);
  374. }
  375. return $request;
  376. }
  377. }