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.

123 lines
3.4 KiB

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