<?php |
<?php |
|
|
/** |
/** |
* HTTP REST Client for CouchDB API |
* HTTP REST Client for CouchDB API |
*/ |
*/ |
class SetteeRestClient { |
class SetteeRestClient { |
|
|
/** |
/** |
* HTTP Timeout in Milliseconds |
* HTTP Timeout in Milliseconds |
*/ |
*/ |
const HTTP_TIMEOUT = 2000; |
const HTTP_TIMEOUT = 2000; |
|
|
private $base_url; |
private $base_url; |
private $curl; |
private $curl; |
|
|
private static $curl_workers = array(); |
private static $curl_workers = array(); |
|
|
/** |
/** |
* Singleton factory method |
* Singleton factory method |
*/ |
*/ |
static function get_instance($base_url) { |
static function get_instance($base_url) { |
|
|
if (empty(self::$curl_workers[$base_url])) { |
if (empty(self::$curl_workers[$base_url])) { |
self::$curl_workers[$base_url] = new SetteeRestClient($base_url); |
self::$curl_workers[$base_url] = new SetteeRestClient($base_url); |
} |
} |
|
|
return self::$curl_workers[$base_url]; |
return self::$curl_workers[$base_url]; |
} |
} |
|
|
/** |
/** |
* Class constructor |
* Class constructor |
*/ |
*/ |
private function __construct($base_url) { |
private function __construct($base_url) { |
$this->base_url = $base_url; |
$this->base_url = $base_url; |
|
|
$curl = curl_init(); |
$curl = curl_init(); |
curl_setopt($curl, CURLOPT_USERAGENT, "Settee CouchDB Client/1.0"); |
curl_setopt($curl, CURLOPT_USERAGENT, "Settee CouchDB Client/1.0"); |
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/json')); |
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/json')); |
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); |
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); |
curl_setopt($curl, CURLOPT_HEADER, 0); |
curl_setopt($curl, CURLOPT_HEADER, 0); |
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); |
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); |
curl_setopt($curl, CURLOPT_TIMEOUT_MS, self::HTTP_TIMEOUT); |
curl_setopt($curl, CURLOPT_TIMEOUT_MS, self::HTTP_TIMEOUT); |
curl_setopt($curl, CURLOPT_FORBID_REUSE, false); // Connection-pool for CURL |
curl_setopt($curl, CURLOPT_FORBID_REUSE, false); // Connection-pool for CURL |
|
|
$this->curl = $curl; |
$this->curl = $curl; |
|
|
} |
} |
|
|
/** |
/** |
* Class destructor cleans up any resources |
* Class destructor cleans up any resources |
*/ |
*/ |
function __destruct() { |
function __destruct() { |
curl_close($this->curl); |
curl_close($this->curl); |
} |
} |
|
|
/** |
/** |
* HTTP HEAD |
* HTTP HEAD |
* |
* |
* @return |
* @return |
* Raw HTTP Headers of the response. |
* Raw HTTP Headers of the response. |
* |
* |
* @see: http://www.php.net/manual/en/context.params.php |
* @see: http://www.php.net/manual/en/context.params.php |
* |
* |
*/ |
*/ |
function http_head($uri) { |
function http_head($uri) { |
curl_setopt($this->curl, CURLOPT_HEADER, 1); |
curl_setopt($this->curl, CURLOPT_HEADER, 1); |
|
|
$full_url = $this->get_full_url($uri); |
$full_url = $this->get_full_url($uri); |
curl_setopt($this->curl, CURLOPT_URL, $full_url); |
curl_setopt($this->curl, CURLOPT_URL, $full_url); |
curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, 'HEAD'); |
curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, 'HEAD'); |
curl_setopt($this->curl, CURLOPT_NOBODY, true); |
curl_setopt($this->curl, CURLOPT_NOBODY, true); |
|
|
|
|
$response = curl_exec($this->curl); |
$response = curl_exec($this->curl); |
// Restore default values |
// Restore default values |
curl_setopt($this->curl, CURLOPT_NOBODY, false); |
curl_setopt($this->curl, CURLOPT_NOBODY, false); |
curl_setopt($this->curl, CURLOPT_HEADER, false); |
curl_setopt($this->curl, CURLOPT_HEADER, false); |
|
|
$resp_code = curl_getinfo($this->curl, CURLINFO_HTTP_CODE); |
$resp_code = curl_getinfo($this->curl, CURLINFO_HTTP_CODE); |
if ($resp_code == 404 ) { |
if ($resp_code == 404 ) { |
throw new SetteeRestClientException("Couch document not found at: '$full_url'"); |
throw new SetteeRestClientException("Couch document not found at: '$full_url'"); |
} |
} |
|
|
if (function_exists('http_parse_headers')) { |
if (function_exists('http_parse_headers')) { |
$headers = http_parse_headers($response); |
$headers = http_parse_headers($response); |
} |
} |
else { |
else { |
$headers = $this->_http_parse_headers($response); |
$headers = $this->_http_parse_headers($response); |
} |
} |
|
|
return $headers; |
return $headers; |
} |
} |
|
|
/** |
/** |
* Backup PHP impl. for when PECL http_parse_headers() function is not available |
* Backup PHP impl. for when PECL http_parse_headers() function is not available |
* |
* |
* @param $header |
* @param $header |
* @return array |
* @return array |
* @source http://www.php.net/manual/en/function.http-parse-headers.php#77241 |
* @source http://www.php.net/manual/en/function.http-parse-headers.php#77241 |
*/ |
*/ |
private function _http_parse_headers( $header ) { |
private function _http_parse_headers( $header ) { |
$retVal = array(); |
$retVal = array(); |
$fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $header)); |
$fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $header)); |
foreach( $fields as $field ) { |
foreach( $fields as $field ) { |
if( preg_match('/([^:]+): (.+)/m', $field, $match) ) { |
if( preg_match('/([^:]+): (.+)/m', $field, $match) ) { |
$match[1] = preg_replace('/(?<=^|[\x09\x20\x2D])./e', 'strtoupper("\0")', strtolower(trim($match[1]))); |
$match[1] = preg_replace('/(?<=^|[\x09\x20\x2D])./e', 'strtoupper("\0")', strtolower(trim($match[1]))); |
if( isset($retVal[$match[1]]) ) { |
if( isset($retVal[$match[1]]) ) { |
$retVal[$match[1]] = array($retVal[$match[1]], $match[2]); |
$retVal[$match[1]] = array($retVal[$match[1]], $match[2]); |
} else { |
} else { |
$retVal[$match[1]] = trim($match[2]); |
$retVal[$match[1]] = trim($match[2]); |
} |
} |
} |
} |
} |
} |
return $retVal; |
return $retVal; |
} |
} |
|
|
/** |
/** |
* HTTP GET |
* HTTP GET |
*/ |
*/ |
function http_get($uri, $data = array()) { |
function http_get($uri, $data = array()) { |
$data = (is_array($data)) ? http_build_query($data) : $data; |
$data = (is_array($data)) ? http_build_query($data) : $data; |
if (!empty($data)) { |
if (!empty($data)) { |
$uri .= "?$data"; |
$uri .= "?$data"; |
} |
} |
return $this->http_request('GET', $uri); |
return $this->http_request('GET', $uri); |
} |
} |
|
|
/** |
/** |
* HTTP PUT |
* HTTP PUT |
*/ |
*/ |
function http_put($uri, $data = array()) { |
function http_put($uri, $data = array()) { |
return $this->http_request('PUT', $uri, $data); |
return $this->http_request('PUT', $uri, $data); |
} |
} |
|
|
/** |
/** |
* HTTP DELETE |
* HTTP DELETE |
*/ |
*/ |
function http_delete($uri, $data = array()) { |
function http_delete($uri, $data = array()) { |
return $this->http_request('DELETE', $uri, $data); |
return $this->http_request('DELETE', $uri, $data); |
} |
} |
|
|
/** |
/** |
* Generic implementation of a HTTP Request. |
* Generic implementation of a HTTP Request. |
* |
* |
* @param $http_method |
* @param $http_method |
* @param $uri |
* @param $uri |
* @param array $data |
* @param array $data |
* @return |
* @return |
* an array containing json and decoded versions of the response. |
* an array containing json and decoded versions of the response. |
*/ |
*/ |
private function http_request($http_method, $uri, $data = array()) { |
private function http_request($http_method, $uri, $data = array()) { |
$data = (is_array($data)) ? http_build_query($data) : $data; |
$data = (is_array($data)) ? http_build_query($data) : $data; |
|
|
if (!empty($data)) { |
if (!empty($data)) { |
curl_setopt($this->curl, CURLOPT_HTTPHEADER, array('Content-Length: ' . strlen($data))); |
curl_setopt($this->curl, CURLOPT_HTTPHEADER, array('Content-Length: ' . strlen($data))); |
curl_setopt($this->curl, CURLOPT_POSTFIELDS, $data); |
curl_setopt($this->curl, CURLOPT_POSTFIELDS, $data); |
} |
} |
|
|
curl_setopt($this->curl, CURLOPT_URL, $this->get_full_url($uri)); |
curl_setopt($this->curl, CURLOPT_URL, $this->get_full_url($uri)); |
curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $http_method); |
curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $http_method); |
|
|
$response = curl_exec($this->curl); |
$response = curl_exec($this->curl); |
$response_decoded = $this->decode_response($response); |
$response_decoded = $this->decode_response($response); |
$response = array('json' => $response, 'decoded'=>$response_decoded); |
$response = array('json' => $response, 'decoded'=>$response_decoded); |
|
|
$this->check_status($response,$uri); |
$this->check_status($response,$uri); |
|
|
return $response; |
return $response; |
} |
} |
|
|
/** |
/** |
* Check http status for safe return codes |
* Check http status for safe return codes |
* |
* |
* @throws SetteeRestClientException |
* @throws SetteeRestClientException |
*/ |
*/ |
private function check_status($response,$uri) { |
private function check_status($response,$uri) { |
$resp_code = curl_getinfo($this->curl, CURLINFO_HTTP_CODE); |
$resp_code = curl_getinfo($this->curl, CURLINFO_HTTP_CODE); |
|
|
if ($resp_code < 199 || $resp_code > 399 || !empty($response['decoded']->error)) { |
if ($resp_code < 199 || $resp_code > 399 || !empty($response['decoded']->error)) { |
$msg = "CouchDB returned: \"HTTP 1.1. $resp_code\". ERROR: " . $response['json'] . $uri; |
$msg = "CouchDB returned: \"HTTP 1.1. $resp_code\". ERROR: " . $response['json'] . $uri; |
throw new SetteeRestClientException($msg); |
throw new SetteeRestClientException($msg); |
} |
} |
} |
} |
|
|
/** |
/** |
* @param $path |
* @param $path |
* Full path to a file (e.g. as returned by PHP's realpath function). |
* Full path to a file (e.g. as returned by PHP's realpath function). |
* @return void |
* @return void |
*/ |
*/ |
public function file_mime_type ($path) { |
public function file_mime_type ($path) { |
$ftype = 'application/octet-stream'; |
$ftype = 'application/octet-stream'; |
|
|
if (function_exists("finfo_file")) { |
if (function_exists("finfo_file")) { |
$finfo = new finfo(FILEINFO_MIME_TYPE | FILEINFO_SYMLINK); |
$finfo = new finfo(FILEINFO_MIME_TYPE | FILEINFO_SYMLINK); |
$fres = $finfo->file($path); |
$fres = $finfo->file($path); |
if (is_string($fres) && !empty($fres)) { |
if (is_string($fres) && !empty($fres)) { |
$ftype = $fres; |
$ftype = $fres; |
} |
} |
} |
} |
|
|
return $ftype; |
return $ftype; |
} |
} |
|
|
/** |
/** |
* @param $content |
* @param $content |
* content of a file in a string buffer format. |
* content of a file in a string buffer format. |
* @return void |
* @return void |
*/ |
*/ |
public function content_mime_type ($content) { |
public function content_mime_type ($content) { |
$ftype = 'application/octet-stream'; |
$ftype = 'application/octet-stream'; |
|
|
if (function_exists("finfo_file")) { |
if (function_exists("finfo_file")) { |
$finfo = new finfo(FILEINFO_MIME_TYPE | FILEINFO_SYMLINK); |
$finfo = new finfo(FILEINFO_MIME_TYPE | FILEINFO_SYMLINK); |
$fres = $finfo->buffer($content); |
$fres = $finfo->buffer($content); |
if (is_string($fres) && !empty($fres)) { |
if (is_string($fres) && !empty($fres)) { |
$ftype = $fres; |
$ftype = $fres; |
} |
} |
} |
} |
|
|
return $ftype; |
return $ftype; |
} |
} |
|
|
|
|
/** |
/** |
* |
* |
* @param $json |
* @param $json |
* json-encoded response from CouchDB |
* json-encoded response from CouchDB |
* |
* |
* @return |
* @return |
* decoded PHP object |
* decoded PHP object |
*/ |
*/ |
private function decode_response($json) { |
private function decode_response($json) { |
return json_decode($json); |
return json_decode($json); |
} |
} |
|
|
/** |
/** |
* Get full URL from a partial one |
* Get full URL from a partial one |
*/ |
*/ |
private function get_full_url($uri) { |
private function get_full_url($uri) { |
// We do not want "/", "?", "&" and "=" separators to be encoded!!! |
// We do not want "/", "?", "&" and "=" separators to be encoded!!! |
$uri = str_replace(array('%2F', '%3F', '%3D', '%26'), array('/', '?', '=', '&'), urlencode($uri)); |
$uri = str_replace(array('%2F', '%3F', '%3D', '%26'), array('/', '?', '=', '&'), urlencode($uri)); |
return $this->base_url . '/' . $uri; |
return $this->base_url . '/' . $uri; |
} |
} |
} |
} |
|
|
class SetteeRestClientException extends Exception {} |
class SetteeRestClientException extends Exception {} |
|
|