Skip to content

Instantly share code, notes, and snippets.

@gyuchang
Created January 26, 2013 00:13
Show Gist options
  • Select an option

  • Save gyuchang/4639068 to your computer and use it in GitHub Desktop.

Select an option

Save gyuchang/4639068 to your computer and use it in GitHub Desktop.
simple wrapper class for cURL
<?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