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