Curl.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. <?php
  2. namespace components;
  3. use Yii;
  4. use yii\base\Exception;
  5. use yii\helpers\Json;
  6. class Curl
  7. {
  8. /**
  9. * @var string
  10. * * Holds response data right after sending a request.
  11. */
  12. public $httpType = 'http';
  13. /**
  14. * @var string|boolean
  15. * Holds response data right after sending a request.
  16. */
  17. public $response = null;
  18. /**
  19. * @var null|integer
  20. * Error code holder: https://curl.haxx.se/libcurl/c/libcurl-errors.html
  21. */
  22. public $errorCode = null;
  23. /**
  24. * @var null|string
  25. * Error text holder: http://php.net/manual/en/function.curl-strerror.php
  26. */
  27. public $errorText = null;
  28. /**
  29. * @var integer HTTP-Status Code
  30. * This value will hold HTTP-Status Code. False if request was not successful.
  31. */
  32. public $responseCode = null;
  33. /**
  34. * @var string|null HTTP Response Charset
  35. * (taken from Content-type header)
  36. */
  37. public $responseCharset = null;
  38. /**
  39. * @var int HTTP Response Length
  40. * (taken from Content-length header, or strlen() of downloaded content)
  41. */
  42. public $responseLength = -1;
  43. /**
  44. * @var string|null HTTP Response Content Type
  45. * (taken from Content-type header)
  46. */
  47. public $responseType = null;
  48. /**
  49. * @var array|null HTTP Response headers
  50. * Lists response header in an array if CURLOPT_HEADER is set to true.
  51. */
  52. public $responseHeaders = null;
  53. /**
  54. * @var array HTTP-Status Code
  55. * Custom options holder
  56. */
  57. protected $_options = [];
  58. /**
  59. * @var array
  60. * Hold array of get params to send with the request
  61. */
  62. protected $_getParams = [];
  63. /**
  64. * @var array
  65. * Hold array of post params to send with the request
  66. */
  67. protected $_postParams = [];
  68. /**
  69. * @var resource|null
  70. * Holds cURL-Handler
  71. */
  72. public $curl = null;
  73. /**
  74. * @var string
  75. * hold base URL
  76. */
  77. protected $_baseUrl = '';
  78. /**
  79. * @var array default curl options
  80. * Default curl options
  81. */
  82. protected $_defaultOptions = [
  83. CURLOPT_USERAGENT => 'Yii2-Curl-Agent',
  84. CURLOPT_TIMEOUT => 10,
  85. CURLOPT_CONNECTTIMEOUT => 10,
  86. CURLOPT_RETURNTRANSFER => true,
  87. CURLOPT_HEADER => true,
  88. CURLOPT_SSL_VERIFYPEER => false,
  89. CURLOPT_SSL_VERIFYHOST => 2,
  90. ];
  91. // ############################################### class methods // ##############################################
  92. /**
  93. * Start performing GET-HTTP-Request
  94. *
  95. * @param string $url
  96. * @param boolean $raw if response body contains JSON and should be decoded
  97. *
  98. * @return mixed response
  99. */
  100. public function get($url, $raw = true)
  101. {
  102. $this->_baseUrl = $url;
  103. return $this->_httpRequest('GET', $raw);
  104. }
  105. /**
  106. * Start performing HEAD-HTTP-Request
  107. *
  108. * @param string $url
  109. *
  110. * @return mixed response
  111. */
  112. public function head($url)
  113. {
  114. $this->_baseUrl = $url;
  115. return $this->_httpRequest('HEAD');
  116. }
  117. /**
  118. * Start performing POST-HTTP-Request
  119. *
  120. * @param string $url
  121. * @param boolean $raw if response body contains JSON and should be decoded
  122. *
  123. * @return mixed response
  124. */
  125. public function post($url, $raw = true)
  126. {
  127. $this->_baseUrl = $url;
  128. return $this->_httpRequest('POST', $raw);
  129. }
  130. /**
  131. * Start performing PUT-HTTP-Request
  132. *
  133. * @param string $url
  134. * @param boolean $raw if response body contains JSON and should be decoded
  135. *
  136. * @return mixed response
  137. */
  138. public function put($url, $raw = true)
  139. {
  140. $this->_baseUrl = $url;
  141. return $this->_httpRequest('PUT', $raw);
  142. }
  143. /**
  144. * Start performing PATCH-HTTP-Request
  145. *
  146. * @param string $url
  147. * @param boolean $raw if response body contains JSON and should be decoded
  148. *
  149. * @return mixed response
  150. */
  151. public function patch($url, $raw = true)
  152. {
  153. $this->_baseUrl = $url;
  154. $this->setHeaders([
  155. 'X-HTTP-Method-Override' => 'PATCH'
  156. ]);
  157. return $this->_httpRequest('POST',$raw);
  158. }
  159. /**
  160. * Start performing DELETE-HTTP-Request
  161. *
  162. * @param string $url
  163. * @param boolean $raw if response body contains JSON and should be decoded
  164. *
  165. * @return mixed response
  166. */
  167. public function delete($url, $raw = true)
  168. {
  169. $this->_baseUrl = $url;
  170. return $this->_httpRequest('DELETE', $raw);
  171. }
  172. /**
  173. * Set curl option
  174. *
  175. * @param string $key
  176. * @param mixed $value
  177. *
  178. * @return $this
  179. */
  180. public function setOption($key, $value)
  181. {
  182. //set value
  183. if (array_key_exists($key, $this->_defaultOptions) && $key !== CURLOPT_WRITEFUNCTION) {
  184. $this->_defaultOptions[$key] = $value;
  185. } else {
  186. $this->_options[$key] = $value;
  187. }
  188. //return self
  189. return $this;
  190. }
  191. /**
  192. * Set get params
  193. *
  194. * @param array $params
  195. * @return $this
  196. */
  197. public function setGetParams($params)
  198. {
  199. if (is_array($params)) {
  200. foreach ($params as $key => $value) {
  201. $this->_getParams[$key] = $value;
  202. }
  203. }
  204. //return self
  205. return $this;
  206. }
  207. /**
  208. * Set get params
  209. *
  210. * @param array $params
  211. * @return $this
  212. */
  213. public function setPostParams($params)
  214. {
  215. if (is_array($params)) {
  216. $this->setOption(
  217. CURLOPT_POSTFIELDS,
  218. http_build_query($params)
  219. );
  220. }
  221. //return self
  222. return $this;
  223. }
  224. /**
  225. * Set raw post data allows you to post any data format.
  226. *
  227. * @param mixed $data
  228. * @return $this
  229. */
  230. public function setRawPostData($data)
  231. {
  232. $this->setOption(
  233. CURLOPT_POSTFIELDS,
  234. $data
  235. );
  236. //return self
  237. return $this;
  238. }
  239. /**
  240. * Set get params
  241. *
  242. * @param string $data
  243. * @return $this
  244. */
  245. public function setRequestBody($data)
  246. {
  247. if (is_string($data)) {
  248. $this->setOption(
  249. CURLOPT_POSTFIELDS,
  250. $data
  251. );
  252. }
  253. //return self
  254. return $this;
  255. }
  256. /**
  257. * Get URL - return URL parsed with given params
  258. *
  259. * @return string The full URL with parsed get params
  260. */
  261. public function getUrl()
  262. {
  263. if (Count($this->_getParams) > 0) {
  264. return $this->_baseUrl.'?'.http_build_query($this->_getParams);
  265. } else {
  266. return $this->_baseUrl;
  267. }
  268. }
  269. /**
  270. * Set curl options
  271. *
  272. * @param array $options
  273. *
  274. * @return $this
  275. */
  276. public function setOptions($options)
  277. {
  278. $this->_options = $options + $this->_options;
  279. return $this;
  280. }
  281. /**
  282. * Set multiple headers for request.
  283. *
  284. * @param array $headers
  285. *
  286. * @return $this
  287. */
  288. public function setHeaders($headers)
  289. {
  290. if (is_array($headers)) {
  291. //init
  292. $parsedHeader = [];
  293. //collect currently set headers
  294. foreach ($this->getRequestHeaders() as $header => $value) {
  295. array_push($parsedHeader, $header.':'.$value);
  296. }
  297. //parse header into right format key:value
  298. foreach ($headers as $header => $value) {
  299. array_push($parsedHeader, $header.':'.$value);
  300. }
  301. //set headers
  302. $this->setOption(
  303. CURLOPT_HTTPHEADER,
  304. $parsedHeader
  305. );
  306. }
  307. return $this;
  308. }
  309. /**
  310. * Set a single header for request.
  311. *
  312. * @param string $header
  313. * @param string $value
  314. *
  315. * @return $this
  316. */
  317. public function setHeader($header, $value)
  318. {
  319. //init
  320. $parsedHeader = [];
  321. //collect currently set headers
  322. foreach ($this->getRequestHeaders() as $headerToSet => $valueToSet) {
  323. array_push($parsedHeader, $headerToSet.':'.$valueToSet);
  324. }
  325. //add override new header
  326. if (strlen($header) > 0) {
  327. array_push($parsedHeader, $header.':'.$value);
  328. }
  329. //set headers
  330. $this->setOption(
  331. CURLOPT_HTTPHEADER,
  332. $parsedHeader
  333. );
  334. return $this;
  335. }
  336. /**
  337. * Unset a single header.
  338. *
  339. * @param string $header
  340. *
  341. * @return $this
  342. */
  343. public function unsetHeader($header)
  344. {
  345. //init
  346. $parsedHeader = [];
  347. //collect currently set headers and filter "unset" header param.
  348. foreach ($this->getRequestHeaders() as $headerToSet => $valueToSet) {
  349. if ($header !== $headerToSet) {
  350. array_push($parsedHeader, $headerToSet.':'.$valueToSet);
  351. }
  352. }
  353. //set headers
  354. $this->setOption(
  355. CURLOPT_HTTPHEADER,
  356. $parsedHeader
  357. );
  358. return $this;
  359. }
  360. /**
  361. * Get all request headers as key:value array
  362. *
  363. * @return array
  364. */
  365. public function getRequestHeaders()
  366. {
  367. //Init
  368. $requestHeaders = $this->getOption(CURLOPT_HTTPHEADER);
  369. $parsedRequestHeaders = [];
  370. if (is_array($requestHeaders)) {
  371. foreach ($requestHeaders as $headerValue) {
  372. list ($key, $value) = explode(':', $headerValue, 2);
  373. $parsedRequestHeaders[$key] = $value;
  374. }
  375. }
  376. return $parsedRequestHeaders;
  377. }
  378. /**
  379. * Get specific request header as key:value array
  380. *
  381. * @param string $headerKey
  382. *
  383. * @return string|null
  384. */
  385. public function getRequestHeader($headerKey)
  386. {
  387. //Init
  388. $parsedRequestHeaders = $this->getRequestHeaders();
  389. return isset($parsedRequestHeaders[$headerKey]) ? $parsedRequestHeaders[$headerKey] : null;
  390. }
  391. /**
  392. * Unset a single curl option
  393. *
  394. * @param string $key
  395. *
  396. * @return $this
  397. */
  398. public function unsetOption($key)
  399. {
  400. //reset a single option if its set already
  401. if (isset($this->_options[$key])) {
  402. unset($this->_options[$key]);
  403. }
  404. return $this;
  405. }
  406. /**
  407. * Unset all curl option, excluding default options.
  408. *
  409. * @return $this
  410. */
  411. public function unsetOptions()
  412. {
  413. //reset all options
  414. if (isset($this->_options)) {
  415. $this->_options = [];
  416. }
  417. return $this;
  418. }
  419. /**
  420. * Total reset of options, responses, etc.
  421. *
  422. * @return $this
  423. */
  424. public function reset()
  425. {
  426. if ($this->curl !== null) {
  427. curl_close($this->curl); //stop curl
  428. }
  429. //reset all options
  430. if (isset($this->_options)) {
  431. $this->_options = [];
  432. }
  433. //reset response & status params
  434. $this->curl = null;
  435. $this->errorCode = null;
  436. $this->response = null;
  437. $this->responseCode = null;
  438. $this->responseCharset = null;
  439. $this->responseLength = -1;
  440. $this->responseType = null;
  441. $this->errorText = null;
  442. $this->_postParams = [];
  443. $this->_getParams = [];
  444. return $this;
  445. }
  446. /**
  447. * Return a single option
  448. *
  449. * @param string|integer $key
  450. * @return mixed|boolean
  451. */
  452. public function getOption($key)
  453. {
  454. //get merged options depends on default and user options
  455. $mergesOptions = $this->getOptions();
  456. //return value or false if key is not set.
  457. return isset($mergesOptions[$key]) ? $mergesOptions[$key] : false;
  458. }
  459. /**
  460. * Return merged curl options and keep keys!
  461. *
  462. * @return array
  463. */
  464. public function getOptions()
  465. {
  466. return $this->_options + $this->_defaultOptions;
  467. }
  468. /**
  469. * Get curl info according to http://php.net/manual/de/function.curl-getinfo.php
  470. *
  471. * @param null $opt
  472. * @return array|mixed
  473. */
  474. public function getInfo($opt = null)
  475. {
  476. if ($this->curl !== null && $opt === null) {
  477. return curl_getinfo($this->curl);
  478. } elseif ($this->curl !== null && $opt !== null) {
  479. return curl_getinfo($this->curl, $opt);
  480. } else {
  481. return [];
  482. }
  483. }
  484. /**
  485. * Performs HTTP request
  486. *
  487. * @param string $method
  488. * @param boolean $raw if response body contains JSON and should be decoded -> helper.
  489. *
  490. * @throws Exception if request failed
  491. *
  492. * @return mixed
  493. */
  494. protected function _httpRequest($method, $raw = false)
  495. {
  496. //set request type and writer function
  497. $this->setOption(CURLOPT_CUSTOMREQUEST, strtoupper($method));
  498. //check if method is head and set no body
  499. if ($method === 'HEAD') {
  500. $this->setOption(CURLOPT_NOBODY, true);
  501. $this->unsetOption(CURLOPT_WRITEFUNCTION);
  502. }
  503. //setup error reporting and profiling
  504. if (YII_DEBUG) {
  505. Yii::trace('Start sending cURL-Request: '.$this->getUrl().'\n', __METHOD__);
  506. Yii::beginProfile($method.' '.$this->_baseUrl.'#'.md5(serialize($this->getOption(CURLOPT_POSTFIELDS))), __METHOD__);
  507. }
  508. /**
  509. * proceed curl
  510. */
  511. $curlOptions = $this->getOptions();
  512. $this->curl = curl_init($this->getUrl());
  513. curl_setopt_array($this->curl, $curlOptions);
  514. $response = curl_exec($this->curl);
  515. //check if curl was successful
  516. if ($response === false) {
  517. //set error code
  518. $this->errorCode = curl_errno($this->curl);
  519. $this->errorText = curl_strerror($this->errorCode);
  520. switch ($this->errorCode) {
  521. // 7, 28 = timeout
  522. case 7:
  523. case 28:
  524. $this->responseCode = 'timeout';
  525. return false;
  526. break;
  527. default:
  528. return false;
  529. break;
  530. }
  531. }
  532. //extract header / body data if CURLOPT_HEADER are set to true
  533. if (isset($curlOptions[CURLOPT_HEADER]) && $curlOptions[CURLOPT_HEADER]) {
  534. $this->response = $this->_extractCurlBody($response);
  535. $this->responseHeaders = $this->_extractCurlHeaders($response);
  536. } else {
  537. $this->response = $response;
  538. }
  539. // Extract additional curl params
  540. $this->_extractAdditionalCurlParameter();
  541. //end yii debug profile
  542. if (YII_DEBUG) {
  543. Yii::endProfile($method.' '.$this->getUrl().'#'.md5(serialize($this->getOption(CURLOPT_POSTFIELDS))), __METHOD__);
  544. }
  545. //check responseCode and return data/status
  546. if ($this->getOption(CURLOPT_CUSTOMREQUEST) === 'HEAD') {
  547. return true;
  548. } else {
  549. $this->response = $raw ? $this->response : Json::decode($this->response);
  550. return $this->response;
  551. }
  552. }
  553. /**
  554. * Extract additional curl params protected class helper
  555. */
  556. protected function _extractAdditionalCurlParameter ()
  557. {
  558. /**
  559. * retrieve response code
  560. */
  561. $this->responseCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
  562. /**
  563. * try extract response type & charset.
  564. */
  565. $this->responseType = curl_getinfo($this->curl, CURLINFO_CONTENT_TYPE);
  566. if (!is_null($this->responseType) && count(explode(';', $this->responseType)) > 1) {
  567. list($this->responseType, $possibleCharset) = explode(';', $this->responseType);
  568. //extract charset
  569. if (preg_match('~^charset=(.+?)$~', trim($possibleCharset), $matches) && isset($matches[1])) {
  570. $this->responseCharset = strtolower($matches[1]);
  571. }
  572. }
  573. /**
  574. * try extract response length
  575. */
  576. $this->responseLength = curl_getinfo($this->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD);
  577. if((int)$this->responseLength == -1) {
  578. $this->responseLength = strlen($this->response);
  579. }
  580. }
  581. /**
  582. * Extract body curl data from response
  583. *
  584. * @param string $response
  585. * @return string
  586. */
  587. protected function _extractCurlBody ($response)
  588. {
  589. return substr($response, $this->getInfo(CURLINFO_HEADER_SIZE));
  590. }
  591. /**
  592. * Extract header curl data from response
  593. *
  594. * @param string $response
  595. * @return array
  596. */
  597. protected function _extractCurlHeaders ($response)
  598. {
  599. //Init
  600. $headers = [];
  601. $headerText = substr($response, 0, strpos($response, "\r\n\r\n"));
  602. foreach (explode("\r\n", $headerText) as $i => $line) {
  603. if ($i === 0) {
  604. $headers['http_code'] = $line;
  605. } else {
  606. list ($key, $value) = explode(':', $line, 2);
  607. $headers[$key] = ltrim($value);
  608. }
  609. }
  610. return $headers;
  611. }
  612. }