CurlTransport.php 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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;
  9. /**
  10. * CurlTransport sends HTTP messages using [Client URL Library (cURL)](http://php.net/manual/en/book.curl.php)
  11. *
  12. * Note: this transport requires PHP 'curl' extension installed.
  13. *
  14. * For this transport, you may setup request options as [cURL Options](http://php.net/manual/en/function.curl-setopt.php)
  15. *
  16. * @author Paul Klimov <klimov.paul@gmail.com>
  17. * @since 2.0
  18. */
  19. class CurlTransport extends Transport
  20. {
  21. /**
  22. * {@inheritdoc}
  23. */
  24. public function send($request)
  25. {
  26. $request->beforeSend();
  27. $curlOptions = $this->prepare($request);
  28. $curlResource = $this->initCurl($curlOptions);
  29. $responseHeaders = [];
  30. $this->setHeaderOutput($curlResource, $responseHeaders);
  31. $token = $request->client->createRequestLogToken($request->getMethod(), $curlOptions[CURLOPT_URL], $curlOptions[CURLOPT_HTTPHEADER], print_r($request->getContent(), true));
  32. Yii::info($token, __METHOD__);
  33. Yii::beginProfile($token, __METHOD__);
  34. $responseContent = curl_exec($curlResource);
  35. Yii::endProfile($token, __METHOD__);
  36. // check cURL error
  37. $errorNumber = curl_errno($curlResource);
  38. $errorMessage = curl_error($curlResource);
  39. curl_close($curlResource);
  40. if ($errorNumber > 0) {
  41. throw new Exception('Curl error: #' . $errorNumber . ' - ' . $errorMessage, $errorNumber);
  42. }
  43. $response = $request->client->createResponse($responseContent, $responseHeaders);
  44. $request->afterSend($response);
  45. return $response;
  46. }
  47. /**
  48. * {@inheritdoc}
  49. */
  50. public function batchSend(array $requests)
  51. {
  52. $curlBatchResource = curl_multi_init();
  53. $token = '';
  54. $curlResources = [];
  55. $responseHeaders = [];
  56. foreach ($requests as $key => $request) {
  57. /* @var $request Request */
  58. $request->beforeSend();
  59. $curlOptions = $this->prepare($request);
  60. $curlResource = $this->initCurl($curlOptions);
  61. $token .= $request->client->createRequestLogToken($request->getMethod(), $curlOptions[CURLOPT_URL], $curlOptions[CURLOPT_HTTPHEADER], $request->getContent()) . "\n\n";
  62. $responseHeaders[$key] = [];
  63. $this->setHeaderOutput($curlResource, $responseHeaders[$key]);
  64. $curlResources[$key] = $curlResource;
  65. curl_multi_add_handle($curlBatchResource, $curlResource);
  66. }
  67. Yii::info($token, __METHOD__);
  68. Yii::beginProfile($token, __METHOD__);
  69. try {
  70. $isRunning = null;
  71. do {
  72. // See https://bugs.php.net/bug.php?id=61141
  73. if (curl_multi_select($curlBatchResource) === -1) {
  74. usleep(100);
  75. }
  76. do {
  77. $curlExecCode = curl_multi_exec($curlBatchResource, $isRunning);
  78. } while ($curlExecCode === CURLM_CALL_MULTI_PERFORM);
  79. } while ($isRunning > 0 && $curlExecCode === CURLM_OK);
  80. } catch (\Exception $e) {
  81. Yii::endProfile($token, __METHOD__);
  82. throw new Exception($e->getMessage(), $e->getCode(), $e);
  83. }
  84. Yii::endProfile($token, __METHOD__);
  85. $responseContents = [];
  86. foreach ($curlResources as $key => $curlResource) {
  87. $responseContents[$key] = curl_multi_getcontent($curlResource);
  88. curl_multi_remove_handle($curlBatchResource, $curlResource);
  89. }
  90. curl_multi_close($curlBatchResource);
  91. $responses = [];
  92. foreach ($requests as $key => $request) {
  93. $response = $request->client->createResponse($responseContents[$key], $responseHeaders[$key]);
  94. $request->afterSend($response);
  95. $responses[$key] = $response;
  96. }
  97. return $responses;
  98. }
  99. /**
  100. * Prepare request for execution, creating cURL resource for it.
  101. * @param Request $request request instance.
  102. * @return array cURL options.
  103. */
  104. private function prepare($request)
  105. {
  106. $request->prepare();
  107. $curlOptions = $this->composeCurlOptions($request->getOptions());
  108. $method = strtoupper($request->getMethod());
  109. switch ($method) {
  110. case 'POST':
  111. $curlOptions[CURLOPT_POST] = true;
  112. break;
  113. default:
  114. $curlOptions[CURLOPT_CUSTOMREQUEST] = $method;
  115. }
  116. $content = $request->getContent();
  117. if ($method === 'HEAD') {
  118. $curlOptions[CURLOPT_NOBODY] = true;
  119. }
  120. if ($content !== null) {
  121. $curlOptions[CURLOPT_POSTFIELDS] = $content;
  122. }
  123. $curlOptions[CURLOPT_RETURNTRANSFER] = true;
  124. $curlOptions[CURLOPT_URL] = $request->getFullUrl();
  125. $curlOptions[CURLOPT_HTTPHEADER] = $request->composeHeaderLines();
  126. if ($request->getOutputFile()) {
  127. $curlOptions[CURLOPT_FILE] = $request->getOutputFile();
  128. }
  129. return $curlOptions;
  130. }
  131. /**
  132. * Initializes cURL resource.
  133. * @param array $curlOptions cURL options.
  134. * @return resource prepared cURL resource.
  135. */
  136. private function initCurl(array $curlOptions)
  137. {
  138. $curlResource = curl_init();
  139. foreach ($curlOptions as $option => $value) {
  140. curl_setopt($curlResource, $option, $value);
  141. }
  142. return $curlResource;
  143. }
  144. /**
  145. * Composes cURL options from raw request options.
  146. * @param array $options raw request options.
  147. * @return array cURL options, in format: [curl_constant => value].
  148. */
  149. private function composeCurlOptions(array $options)
  150. {
  151. static $optionMap = [
  152. 'protocolVersion' => CURLOPT_HTTP_VERSION,
  153. 'maxRedirects' => CURLOPT_MAXREDIRS,
  154. 'sslCapath' => CURLOPT_CAPATH,
  155. 'sslCafile' => CURLOPT_CAINFO,
  156. 'sslLocalCert' => CURLOPT_SSLCERT,
  157. 'sslLocalPk' => CURLOPT_SSLKEY,
  158. 'sslPassphrase' => CURLOPT_SSLCERTPASSWD,
  159. ];
  160. $curlOptions = [];
  161. foreach ($options as $key => $value) {
  162. if (is_int($key)) {
  163. $curlOptions[$key] = $value;
  164. } else {
  165. if (isset($optionMap[$key])) {
  166. $curlOptions[$optionMap[$key]] = $value;
  167. } else {
  168. $key = strtoupper($key);
  169. if (strpos($key, 'SSL') === 0) {
  170. $key = substr($key, 3);
  171. $constantName = 'CURLOPT_SSL_' . $key;
  172. if (!defined($constantName)) {
  173. $constantName = 'CURLOPT_SSL' . $key;
  174. }
  175. } else {
  176. $constantName = 'CURLOPT_' . strtoupper($key);
  177. }
  178. $curlOptions[constant($constantName)] = $value;
  179. }
  180. }
  181. }
  182. return $curlOptions;
  183. }
  184. /**
  185. * Setup a variable, which should collect the cURL response headers.
  186. * @param resource $curlResource cURL resource.
  187. * @param array $output variable, which should collection headers.
  188. */
  189. private function setHeaderOutput($curlResource, array &$output)
  190. {
  191. curl_setopt($curlResource, CURLOPT_HEADERFUNCTION, function($resource, $headerString) use (&$output) {
  192. $header = trim($headerString, "\n\r");
  193. if (strlen($header) > 0) {
  194. $output[] = $header;
  195. }
  196. return mb_strlen($headerString, '8bit');
  197. });
  198. }
  199. }