Created
January 26, 2013 00:13
-
-
Save gyuchang/4639068 to your computer and use it in GitHub Desktop.
simple wrapper class for cURL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /* | |
| * HTTP/HTTPS class: simple HTTP client | |
| * - it's a wrapper for cURL, geared toward API/server-to-server call | |
| * - if you want to mimic a browser, you should NOT use these classes | |
| */ | |
| class HTTP { | |
| const SCHEME = 'http'; | |
| const PORT = 80; | |
| // default parameters | |
| static $connect_timeout = 3; | |
| static $request_timeout = 3; | |
| static $user_agent = 'Lynx/2.8.4rel.1 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/0.9.6g'; | |
| static $retry = 0; | |
| // cURL info for last request | |
| static $last_curl_info; | |
| // public interfaces | |
| public static function get($host, $uri='/', $params=false, array $headers=array(), $port=false, array $options=array()) { | |
| // prepare request | |
| if (empty($params)) { | |
| $qstr = ''; | |
| } else if (is_array($params)) { | |
| $qstr = strtr(http_build_query($params, '', '&'), array('+'=>'%20')); | |
| } else if (is_scalar($params)) { | |
| $qstr = $params; | |
| } | |
| // merge query string | |
| if ($qstr) { | |
| $uri .= (parse_url($uri, PHP_URL_QUERY) ? '&' : '?') . $qstr; | |
| } | |
| // process response and return | |
| return static::request('GET', $host, $uri, false, $headers, $port, $options); | |
| } | |
| public static function post($host, $uri='/', $request_body=false, array $headers=array(), $port=false, array $options=array()) { | |
| // process response and return | |
| return static::request('POST', $host, $uri, $request_body, $headers, $port, $options); | |
| } | |
| public static function request($method, $host, $uri='/', $request_body=false, array $headers=array(), $port=false, array $options=array()) { | |
| // check input | |
| if (empty($host)) { return array(false, 0, 'INVALID HOST', ''); } | |
| // prepare request | |
| if (is_array($request_body)) { | |
| $headers['Content-Type'] = 'application/x-www-form-urlencoded'; | |
| $request_body = strtr(http_build_query($request_body, '', '&'), array('+'=>'%20')); | |
| } | |
| // execute request | |
| $res = self::_process_request($method, $host, $port, $uri, $headers, $request_body, $options); | |
| return $res; | |
| } | |
| private static function _process_request($verb, $host, $port, $uri, array $headers, $request_body='', array $options=array()) { | |
| if (!$port) { $port = static::PORT; } | |
| $url = sprintf('%s://%s:%d%s', static::SCHEME, $host, $port, $uri); | |
| $ch = curl_init($url); | |
| if (static::SCHEME == 'https') { | |
| curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // do not verify the peer's SSL certificate | |
| if (!empty($options['ssl_cert_file'])) { | |
| curl_setopt($ch, CURLOPT_SSLCERT, $options['ssl_cert_file']); | |
| } | |
| if (!empty($options['ssl_cert_password'])) { | |
| curl_setopt($ch, CURLOPT_SSLCERTPASSWD, $options['ssl_cert_password']); | |
| } | |
| if (!empty($options['ssl_cert_type'])) { | |
| curl_setopt($ch, CURLOPT_SSLCERTTYPE, $options['ssl_cert_type']); | |
| } | |
| if (!empty($options['ssl_key_file'])) { | |
| curl_setopt($ch, CURLOPT_SSLKEY, $options['ssl_key_file']); | |
| } | |
| if (!empty($options['ssl_key_password'])) { | |
| curl_setopt($ch, CURLOPT_SSLKEYPASSWD, $options['ssl_key_password']); | |
| } | |
| if (!empty($options['ssl_key_type'])) { | |
| curl_setopt($ch, CURLOPT_SSLKEYTYPE, $options['ssl_key_type']); | |
| } | |
| } | |
| // support basic auth | |
| if (!empty($options['basic_auth'])) { | |
| curl_setopt($ch, CURLOPT_USERPWD, $options['basic_auth']); | |
| } | |
| // set options | |
| curl_setopt($ch, CURLOPT_FORBID_REUSE, true); // we don't want to reuse old connections since we want the server to use different ports when we retry. | |
| curl_setopt($ch, CURLOPT_FRESH_CONNECT, true); // we don't want to reuse old connections since we want the server to use different ports when we retry. | |
| curl_setopt($ch, CURLOPT_HEADER, true); // include the response header in the response body | |
| curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // return the response body instead of writing it to STDOUT | |
| curl_setopt($ch, CURLINFO_HEADER_OUT, true); // include the request headers with curl_getinfo(). only needed for debugging | |
| $connect_timeout = empty($options['connect_timeout']) ? static::$connect_timeout : $options['connect_timeout']; | |
| if (is_numeric($connect_timeout) && ($connect_timeout > 0)) { | |
| curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $connect_timeout); // max time for curl to initiate a connection with remote server. | |
| } | |
| $request_timeout = empty($options['request_timeout']) ? static::$request_timeout : $options['request_timeout']; | |
| if (is_numeric($request_timeout) && ($request_timeout > 0)) { | |
| curl_setopt($ch, CURLOPT_TIMEOUT, $request_timeout); // total time-limit for curl to execute, this includes connection time plus data transfer time | |
| } | |
| $user_agent = empty($options['user_agent']) ? static::$user_agent : $options['user_agent']; | |
| if ($user_agent) { | |
| curl_setopt($ch, CURLOPT_USERAGENT, $user_agent); | |
| } | |
| $retry = static::$retry; | |
| if (isset($options['retry']) && is_numeric($options['retry']) && ($options['retry'] > 0)) { | |
| $retry = $options['retry']; | |
| } | |
| // set HTTP method | |
| $verb = strtoupper($verb); | |
| switch ($verb) { | |
| case 'GET': | |
| curl_setopt($ch, CURLOPT_HTTPGET, true); | |
| break; | |
| case 'POST': | |
| curl_setopt($ch, CURLOPT_POST, true); | |
| break; | |
| case 'HEAD': | |
| curl_setopt($ch, CURLOPT_NOBODY, true); | |
| break; | |
| case 'PUT': | |
| // do not use CURLOPT_PUT because then we have to use CURLOPT_INFILE and CURLOPT_INFILESIZE. | |
| // instead fall through to CURLOPT_CUSTOMREQUEST. The PUT body is set in CURLOPT_POSTFIELDS | |
| case 'DELETE': | |
| case 'CONNECT': | |
| case 'TRACE': | |
| case 'OPTIONS': | |
| curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $verb); | |
| break; | |
| default: | |
| return array(0, -1, array(), "Unsupported method: $verb"); | |
| } | |
| // set request body | |
| if ($request_body) { | |
| if (!is_scalar($request_body)) { | |
| return array(0, -1, array(), 'Unsupported request body : '.(string)$request_body); | |
| } | |
| if (empty($headers['Content-Length'])) { | |
| $headers['Content-Length'] = strlen($request_body); | |
| } | |
| curl_setopt($ch, CURLOPT_POSTFIELDS, $request_body); // this will set Content-Type to 'application/x-www-form-urlencode' since $request_body is a string | |
| } | |
| // set request headers | |
| if (!isset($headers['Expect'])) { $headers['Expect'] = ''; } // do not use Curl's default value | |
| if (!isset($headers['Accept'])) { $headers['Accept'] = ''; } // do not use Curl's default value | |
| $curl_header_out = array(); | |
| foreach ($headers as $k => $v) { | |
| $curl_header_out[] = "{$k}: {$v}"; | |
| } | |
| curl_setopt($ch, CURLOPT_HTTPHEADER, $curl_header_out); | |
| // execute | |
| $try = 0; | |
| while (true) { | |
| $running_time_msecs = round(microtime(true) * 1000); | |
| $response = curl_exec($ch); | |
| $running_time_msecs = round(microtime(true) * 1000) - $running_time_msecs; | |
| $curl_info = curl_getinfo($ch); | |
| $curl_info['response_size'] = strlen($response); | |
| self::$last_curl_info = $curl_info; | |
| if (!empty($response) && !empty($curl_info['header_size'])) { | |
| // success! | |
| break; | |
| } | |
| // since we used CURLOPT_HEADER=true, empty response means an error. so does empty header. HTTP response MUST have at least status line | |
| if ($try >= $retry) { | |
| $curl_errno = curl_errno($ch); | |
| $curl_error = curl_error($ch); // these need to be done before curl_close() | |
| curl_close($ch); | |
| if ($try > 0) { | |
| $curl_error = "{$curl_error} [retry failed after #{$try} attempt]"; | |
| } | |
| return array(0, $curl_errno, $curl_info, $curl_error); | |
| } | |
| usleep(min(2000000, 250000 * (1 << $try))); // sleep between requests: 250ms, 500ms, 1s, 2s, 2s, 2s ... | |
| $try++; | |
| } | |
| // close connection | |
| curl_close($ch); | |
| // split response into head and body | |
| $_headers = explode("\n", trim(substr($response, 0, $curl_info['header_size']))); | |
| $response_body = substr($response, $curl_info['header_size']); | |
| // parse status line | |
| $status = current($_headers); | |
| list($protocol, $code, $text) = explode(' ', $status, 3); | |
| // parse headers | |
| $response_headers = array(); | |
| while ($str = next($_headers)) { | |
| list($name, $value) = explode(': ', $str, 2); | |
| $name = strtoupper($name); | |
| $value = trim($value); | |
| if (isset($response_headers[$name])) { | |
| if (is_array($response_headers[$name])) { | |
| array_push($response_headers[$name], $value); | |
| } else { | |
| $v = $response_headers[$name]; | |
| $response_headers[$name] = array($v, $value); | |
| } | |
| } else { | |
| $response_headers[$name] = $value; | |
| } | |
| } | |
| return array(1, $code, $response_headers, $response_body); | |
| } | |
| } | |
| class HTTPS extends HTTP { | |
| const SCHEME = 'https'; | |
| const PORT = 443; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment