You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

152 lines
5.2 KiB

  1. <?php
  2. namespace p3k\HTTP;
  3. class Curl implements Transport {
  4. protected $_timeout = 4;
  5. protected $_max_redirects = 8;
  6. static protected $_http_version = null;
  7. private $_last_seen_url = null;
  8. private $_last_seen_code = null;
  9. private $_current_headers = [];
  10. private $_current_redirects = [];
  11. private $_debug_header = '';
  12. public function set_max_redirects($max) {
  13. $this->_max_redirects = $max;
  14. }
  15. public function set_timeout($timeout) {
  16. $this->_timeout = $timeout;
  17. }
  18. public function get($url, $headers=[]) {
  19. $ch = curl_init($url);
  20. $this->_set_curlopts($ch, $url);
  21. if($headers)
  22. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  23. $response = curl_exec($ch);
  24. return [
  25. 'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE),
  26. 'header' => implode("\r\n", array_map(function ($a) { return $a[0] . ': ' . $a[1]; }, $this->_current_headers)),
  27. 'body' => $response,
  28. 'redirects' => $this->_current_redirects,
  29. 'error' => self::error_string_from_code(curl_errno($ch)),
  30. 'error_description' => curl_error($ch),
  31. 'url' => $this->_last_seen_url,
  32. 'debug' => $this->_debug_header . "\r\n" . $response
  33. ];
  34. }
  35. public function post($url, $body, $headers=[]) {
  36. $ch = curl_init($url);
  37. $this->_set_curlopts($ch, $url);
  38. curl_setopt($ch, CURLOPT_POST, true);
  39. curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
  40. if($headers)
  41. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  42. $response = curl_exec($ch);
  43. return [
  44. 'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE),
  45. 'header' => implode("\r\n", array_map(function ($a) { return $a[0] . ': ' . $a[1]; }, $this->_current_headers)),
  46. 'body' => $response,
  47. 'redirects' => $this->_current_redirects,
  48. 'error' => self::error_string_from_code(curl_errno($ch)),
  49. 'error_description' => curl_error($ch),
  50. 'url' => $this->_last_seen_url,
  51. 'debug' => $this->_debug_header . "\r\n" . $response
  52. ];
  53. }
  54. public function head($url, $headers=[]) {
  55. $ch = curl_init($url);
  56. $this->_set_curlopts($ch, $url);
  57. if($headers)
  58. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  59. curl_setopt($ch, CURLOPT_NOBODY, true);
  60. $response = curl_exec($ch);
  61. return [
  62. 'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE),
  63. 'header' => implode("\r\n", array_map(function ($a) { return $a[0] . ': ' . $a[1]; }, $this->_current_headers)),
  64. 'redirects' => $this->_current_redirects,
  65. 'error' => self::error_string_from_code(curl_errno($ch)),
  66. 'error_description' => curl_error($ch),
  67. 'url' => $this->_last_seen_url,
  68. 'debug' => $this->_debug_header . "\r\n" . $response
  69. ];
  70. }
  71. private function _set_curlopts($ch, $url) {
  72. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  73. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $this->_max_redirects > 0);
  74. curl_setopt($ch, CURLOPT_MAXREDIRS, $this->_max_redirects);
  75. curl_setopt($ch, CURLOPT_TIMEOUT_MS, round($this->_timeout * 1000));
  76. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 2000);
  77. curl_setopt($ch, CURLOPT_HTTP_VERSION, $this->_http_version());
  78. curl_setopt($ch, CURLOPT_HEADERFUNCTION, [$this, '_header_function']);
  79. }
  80. private function _http_version() {
  81. if (static::$_http_version !== null)
  82. return static::$_http_version;
  83. if (defined('CURL_HTTP_VERSION_2')) { // PHP 7.0.7
  84. static::$_http_version = CURL_HTTP_VERSION_2;
  85. } else if (defined('CURL_HTTP_VERSION_2_0')) { // Recommended in online articles
  86. static::$_http_version = CURL_HTTP_VERSION_2_0;
  87. } else { // Linked curl might be newer than PHP, send (current) INT value anyway.
  88. static::$_http_version = 3;
  89. }
  90. return static::$_http_version;
  91. }
  92. private function _header_function($curl, $header) {
  93. $this->_debug_header .= $header;
  94. $current_url = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL);
  95. if ($current_url !== $this->_last_seen_url) {
  96. if ($this->_last_seen_url !== null) {
  97. $this->_current_redirects[] = [
  98. 'code' => $this->_last_seen_code,
  99. 'from' => $this->_last_seen_url,
  100. 'to' => $current_url,
  101. ];
  102. } else {
  103. $this->_current_redirects = [];
  104. }
  105. $this->_current_headers = [];
  106. $this->_last_seen_url = $current_url;
  107. $this->_last_seen_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
  108. }
  109. $length = strlen($header);
  110. $header = explode(':', $header, 2);
  111. if (count($header) !== 2) {
  112. return $length;
  113. }
  114. $this->_current_headers[] = array_map('trim', [$header[0], $header[1]]);
  115. return $length;
  116. }
  117. public static function error_string_from_code($code) {
  118. switch($code) {
  119. case 0:
  120. return '';
  121. case CURLE_COULDNT_RESOLVE_HOST:
  122. return 'dns_error';
  123. case CURLE_COULDNT_CONNECT:
  124. return 'connect_error';
  125. case CURLE_OPERATION_TIMEDOUT:
  126. return 'timeout';
  127. case CURLE_SSL_CONNECT_ERROR:
  128. return 'ssl_error';
  129. case CURLE_SSL_CERTPROBLEM:
  130. return 'ssl_cert_error';
  131. case CURLE_SSL_CIPHER:
  132. return 'ssl_unsupported_cipher';
  133. case CURLE_SSL_CACERT:
  134. return 'ssl_cert_error';
  135. case CURLE_TOO_MANY_REDIRECTS:
  136. return 'too_many_redirects';
  137. default:
  138. return 'unknown';
  139. }
  140. }
  141. }