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.

138 lines
4.1 KiB

  1. <?php
  2. namespace p3k;
  3. class HTTPStream {
  4. public $timeout = 4;
  5. public $max_redirects = 8;
  6. public static function exception_error_handler($severity, $message, $file, $line) {
  7. if (!(error_reporting() & $severity)) {
  8. // This error code is not included in error_reporting
  9. return;
  10. }
  11. throw new \ErrorException($message, 0, $severity, $file, $line);
  12. }
  13. public function get($url, $headers=[]) {
  14. set_error_handler("p3k\HTTPStream::exception_error_handler");
  15. $context = $this->_stream_context('GET', $url, false, $headers);
  16. return $this->_fetch($url, $context);
  17. }
  18. public function post($url, $body, $headers=[]) {
  19. set_error_handler("p3k\HTTPStream::exception_error_handler");
  20. $context = $this->_stream_context('POST', $url, $body, $headers);
  21. return $this->_fetch($url, $context);
  22. }
  23. public function head($url) {
  24. set_error_handler("p3k\HTTPStream::exception_error_handler");
  25. $context = $this->_stream_context('HEAD', $url);
  26. return $this->_fetch($url, $context);
  27. }
  28. private function _fetch($url, $context) {
  29. $error = false;
  30. try {
  31. $body = file_get_contents($url, false, $context);
  32. // This sets $http_response_header
  33. // see http://php.net/manual/en/reserved.variables.httpresponseheader.php
  34. } catch(\Exception $e) {
  35. $body = false;
  36. $http_response_header = [];
  37. $description = str_replace('file_get_contents(): ', '', $e->getMessage());
  38. $code = 'unknown';
  39. if(preg_match('/getaddrinfo failed/', $description)) {
  40. $code = 'dns_error';
  41. $description = str_replace('php_network_getaddresses: ', '', $description);
  42. }
  43. if(preg_match('/timed out|request failed/', $description)) {
  44. $code = 'timeout';
  45. }
  46. if(preg_match('/certificate/', $description)) {
  47. $code = 'ssl_error';
  48. }
  49. $error = [
  50. 'description' => $description,
  51. 'code' => $code
  52. ];
  53. }
  54. return array(
  55. 'code' => self::parse_response_code($http_response_header),
  56. 'headers' => self::parse_headers($http_response_header),
  57. 'body' => $body,
  58. 'error' => $error ? $error['code'] : false,
  59. 'error_description' => $error ? $error['description'] : false,
  60. 'url' => $url,
  61. );
  62. }
  63. private function _stream_context($method, $url, $body=false, $headers=[]) {
  64. $options = [
  65. 'method' => $method,
  66. 'timeout' => $this->timeout,
  67. 'ignore_errors' => true,
  68. ];
  69. if($body) {
  70. $options['content'] = $body;
  71. }
  72. if($headers) {
  73. $options['header'] = implode("\r\n", $headers);
  74. }
  75. // Special-case appspot.com URLs to not follow redirects.
  76. // https://cloud.google.com/appengine/docs/php/urlfetch/
  77. if(should_follow_redirects($url)) {
  78. $options['follow_location'] = 1;
  79. $options['max_redirects'] = $this->max_redirects;
  80. } else {
  81. $options['follow_location'] = 0;
  82. }
  83. return stream_context_create(['http' => $options]);
  84. }
  85. public static function parse_response_code($headers) {
  86. // When a response is a redirect, we want to find the last occurrence of the HTTP code
  87. $code = false;
  88. foreach($headers as $field) {
  89. if(preg_match('/HTTP\/\d\.\d (\d+)/', $field, $match)) {
  90. $code = $match[1];
  91. }
  92. }
  93. return $code;
  94. }
  95. public static function parse_headers($headers) {
  96. $retVal = array();
  97. foreach($headers as $field) {
  98. if(preg_match('/([^:]+): (.+)/m', $field, $match)) {
  99. $match[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./', function($m) {
  100. return strtoupper($m[0]);
  101. }, strtolower(trim($match[1])));
  102. // If there's already a value set for the header name being returned, turn it into an array and add the new value
  103. $match[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./', function($m) {
  104. return strtoupper($m[0]);
  105. }, strtolower(trim($match[1])));
  106. if(isset($retVal[$match[1]])) {
  107. if(!is_array($retVal[$match[1]]))
  108. $retVal[$match[1]] = array($retVal[$match[1]]);
  109. $retVal[$match[1]][] = $match[2];
  110. } else {
  111. $retVal[$match[1]] = trim($match[2]);
  112. }
  113. }
  114. }
  115. return $retVal;
  116. }
  117. }