| <?php | |
| namespace p3k; | |
| 
 | |
| class HTTPStream { | |
| 
 | |
|   public $timeout = 4; | |
|   public $max_redirects = 8; | |
| 
 | |
|   public static function exception_error_handler($severity, $message, $file, $line) { | |
|     if (!(error_reporting() & $severity)) { | |
|       // This error code is not included in error_reporting | |
|       return; | |
|     } | |
|     throw new \ErrorException($message, 0, $severity, $file, $line); | |
|   } | |
| 
 | |
|   public function get($url) { | |
|     set_error_handler("p3k\HTTPStream::exception_error_handler"); | |
|     $context = $this->_stream_context('GET', $url); | |
|     return $this->_fetch($url, $context); | |
|   } | |
| 
 | |
|   public function post($url, $body, $headers=array()) { | |
|     set_error_handler("p3k\HTTPStream::exception_error_handler"); | |
|     $context = $this->_stream_context('POST', $url, $body, $headers); | |
|     return $this->_fetch($url, $context); | |
|   } | |
| 
 | |
|   public function head($url) { | |
|     set_error_handler("p3k\HTTPStream::exception_error_handler"); | |
|     $context = $this->_stream_context('HEAD', $url); | |
|     return $this->_fetch($url, $context); | |
|   } | |
| 
 | |
|   private function _fetch($url, $context) { | |
|     $error = false; | |
| 
 | |
|     try { | |
|       $body = file_get_contents($url, false, $context); | |
|     } catch(\Exception $e) { | |
|       $body = false; | |
|       $http_response_header = []; | |
|       $description = str_replace('file_get_contents(): ', '', $e->getMessage()); | |
|       $code = 'unknown'; | |
| 
 | |
|       if(preg_match('/getaddrinfo failed/', $description)) { | |
|         $code = 'dns_error'; | |
|         $description = str_replace('php_network_getaddresses: ', '', $description); | |
|       } | |
| 
 | |
|       if(preg_match('/timed out|request failed/', $description)) { | |
|         $code = 'timeout'; | |
|       } | |
| 
 | |
|       if(preg_match('/certificate/', $description)) { | |
|         $code = 'ssl_error'; | |
|       } | |
| 
 | |
|       $error = [ | |
|         'description' => $description, | |
|         'code' => $code | |
|       ]; | |
|     } | |
| 
 | |
|     return array( | |
|       'code' => self::parse_response_code($http_response_header), | |
|       'headers' => self::parse_headers($http_response_header), | |
|       'body' => $body, | |
|       'error' => $error ? $error['code'] : false, | |
|       'error_description' => $error ? $error['description'] : false, | |
|     ); | |
|   } | |
| 
 | |
|   private function _stream_context($method, $url, $body=false, $headers=[]) { | |
|     $options = [ | |
|       'method' => $method, | |
|       'timeout' => $this->timeout, | |
|       'ignore_errors' => true, | |
|     ]; | |
| 
 | |
|     if($body) { | |
|       $options['content'] = $body; | |
|     } | |
| 
 | |
|     if($headers) { | |
|       $options['header'] = $headers; | |
|     } | |
| 
 | |
|     // Special-case appspot.com URLs to not follow redirects. | |
|     // https://cloud.google.com/appengine/docs/php/urlfetch/ | |
|     if(should_follow_redirects($url)) { | |
|       $options['follow_location'] = 1; | |
|       $options['max_redirects'] = $this->max_redirects; | |
|     } else { | |
|       $options['follow_location'] = 0; | |
|     } | |
| 
 | |
|     return stream_context_create(['http' => $options]); | |
|   } | |
| 
 | |
|   public static function parse_response_code($headers) { | |
|     // When a response is a redirect, we want to find the last occurrence of the HTTP code | |
|     $code = false; | |
|     foreach($headers as $field) { | |
|       if(preg_match('/HTTP\/\d\.\d (\d+)/', $field, $match)) { | |
|         $code = $match[1]; | |
|       } | |
|     }     | |
|     return $code; | |
|   } | |
| 
 | |
|   public static function parse_headers($headers) { | |
|     $retVal = array(); | |
|     foreach($headers as $field) { | |
|       if(preg_match('/([^:]+): (.+)/m', $field, $match)) { | |
|         $match[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./', function($m) { | |
|           return strtoupper($m[0]); | |
|         }, strtolower(trim($match[1]))); | |
|         // If there's already a value set for the header name being returned, turn it into an array and add the new value | |
|         $match[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./', function($m) { | |
|           return strtoupper($m[0]); | |
|         }, strtolower(trim($match[1]))); | |
|         if(isset($retVal[$match[1]])) { | |
|           if(!is_array($retVal[$match[1]])) | |
|             $retVal[$match[1]] = array($retVal[$match[1]]); | |
|           $retVal[$match[1]][] = $match[2]; | |
|         } else { | |
|           $retVal[$match[1]] = trim($match[2]); | |
|         } | |
|       } | |
|     } | |
|     return $retVal; | |
|   } | |
| 
 | |
| }
 |