From: Chuck Hagenbuch Date: Wed, 19 Nov 2008 03:49:32 +0000 (-0500) Subject: add Http_Client X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=f5ef174c199166f3923b863f7395f7e08e99155e;p=horde.git add Http_Client --- diff --git a/framework/Http_Client/examples/Horde/Http/Client/get-example-dot-com.php b/framework/Http_Client/examples/Horde/Http/Client/get-example-dot-com.php new file mode 100644 index 000000000..a73cd14bf --- /dev/null +++ b/framework/Http_Client/examples/Horde/Http/Client/get-example-dot-com.php @@ -0,0 +1,20 @@ + + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Http_Client + */ + +require 'Horde/Http/Client.php'; +require 'Horde/Http/Client/Exception.php'; +require 'Horde/Http/Client/Response.php'; + +$client = new Horde_Http_Client(); +$response = $client->GET('http://www.example.com/'); +var_dump($response); +echo $response->getBody(); diff --git a/framework/Http_Client/lib/Horde/Http/Client.php b/framework/Http_Client/lib/Horde/Http/Client.php new file mode 100644 index 000000000..3160fd025 --- /dev/null +++ b/framework/Http_Client/lib/Horde/Http/Client.php @@ -0,0 +1,251 @@ + + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Http_Client + * + * @TODO - add support for http://pecl.php.net/package/pecl_http ? + */ + +/** + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Http_Client + */ +class Horde_Http_Client +{ + /** + * URI to make our next request to + * @var string + */ + protected $_uri = null; + + /** + * Request headers + * @var array + */ + protected $_headers = array(); + + /** + * Proxy server + * @var string + */ + protected $_proxyServer = null; + + /** + * Proxy username + * @var string + */ + protected $_proxyUser = null; + + /** + * Proxy password + * @var string + */ + protected $_proxyPass = null; + + /** + * The most recent HTTP request + * + * An array with these values: + * 'uri' + * 'method' + * 'headers' + * 'data' + * + * @var array + */ + protected $_lastRequest; + + /** + * The most recent HTTP response + * @var Horde_Http_Client_Response + */ + protected $_lastResponse; + + /** + * Horde_Http_Client constructor. + * + * @param array $args Any Http_Client settings to initialize in the + * constructor. Available settings are: + * uri + * headers + * proxyServer + * proxyUser + * proxyPass + */ + public function __construct($args = array()) + { + if (!ini_get('allow_url_fopen')) { + throw new Horde_Http_Client_Exception('allow_url_fopen must be enabled'); + } + + foreach ($args as $key => $val) { + $this->$key = $val; + } + } + + /** + * Set one or more headers + * + * @param mixed $headers A hash of header + value pairs, or a single header name + * @param string $value A header value + */ + public function setHeaders($headers, $value = null) + { + if (!is_array($headers)) { + $headers = array($headers => $value); + } + + foreach ($headers as $header => $value) { + $this->_headers[$header] = $value; + } + } + + /** + * Get the current value of $header + * + * @param string $header Header name to get + * @return string $header's current value + */ + public function getHeader($header) + { + return isset($this->_headers[$header]) ? $this->_headers[$header] : null; + } + + /** + * Send a GET request + * + * @return Horde_Http_Client_Response + */ + public function get($uri = null, $headers = array()) + { + return $this->request('GET', $uri, null, $headers); + } + + /** + * Send a POST request + * + * @return Horde_Http_Client_Response + */ + public function post($uri = null, $data = null, $headers = array()) + { + return $this->request('POST', $uri, $data, $headers); + } + + /** + * Send a PUT request + * + * @return Horde_Http_Client_Response + */ + public function put($uri = null, $data = null, $headers = array()) + { + // FIXME: suport method override (X-Method-Override: PUT). + return $this->request('PUT', $uri, $data, $headers); + } + + /** + * Send a DELETE request + * + * @return Horde_Http_Client_Response + */ + public function delete($uri = null, $headers = array()) + { + // FIXME: suport method override (X-Method-Override: DELETE). + return $this->request('DELETE', $uri, null, $headers); + } + + /** + * Send an HTTP request + * + * @param string $method HTTP request method (GET, PUT, etc.) + * @param string $uri URI to request, if different from $this->uri + * @param mixed $data Request data. Can be an array of form data that will be + * encoded automatically, or a raw string. + * @param array $headers Any headers specific to this request. They will + * be combined with $this->_headers, and override + * headers of the same name for this request only. + * + * @return Horde_Http_Client_Response + */ + public function request($method, $uri = null, $data = null, $headers = array()) + { + if (is_null($uri)) { + $uri = $this->uri; + } + + if (is_array($data)) { + $data = http_build_query($data, '', '&'); + } + + $headers = array_merge($this->_headers, $headers); + + // Store the last request for ease of debugging. + $this->_lastRequest = array( + 'uri' => $uri, + 'method' => $method, + 'headers' => $headers, + 'data' => $data, + ); + + // Stream context config. + $opts = array('http' => array( + 'method' => $method, + 'header' => implode("\n", $headers), + 'content' => $data)); + + // Proxy settings + if ($this->proxyServer) { + $opts['http']['proxy'] = 'tcp://' . $this->proxyServer; + $opts['http']['request_fulluri'] = true; + if ($this->proxyUser && $this->proxyPass) { + $headers['Proxy-Authorization'] = 'Basic ' . base64_encode($this->proxyUser . ':' . $this->proxyPass); + } + } + + $context = stream_context_create($opts); + $stream = @fopen($uri, 'rb', false, $context); + if (!$stream) { + throw new Horde_Http_Client_Exception('Problem with ' . $uri . ': ', error_get_last()); + } + + $meta = stream_get_meta_data($stream); + $headers = isset($meta['wrapper_data']) ? $meta['wrapper_data'] : array(); + + $this->_lastResponse = new Horde_Http_Client_Response($uri, $stream, $headers); + return $this->_lastResponse; + } + + /** + * Get a client parameter + * + * @param string $name The parameter to get. + * @return mixed Parameter value. + */ + public function __get($name) + { + return isset($this->{'_' . $name}) ? $this->{'_' . $name} : null; + } + + /** + * Set a client parameter + * + * @param string $name The parameter to set. + * @param mixed $value Parameter value. + */ + public function __set($name, $value) + { + switch ($name) { + case 'headers': + $this->setHeaders($value); + break; + } + + $this->{'_' . $name} = $value; + } + +} diff --git a/framework/Http_Client/lib/Horde/Http/Client/Exception.php b/framework/Http_Client/lib/Horde/Http/Client/Exception.php new file mode 100644 index 000000000..9941e475a --- /dev/null +++ b/framework/Http_Client/lib/Horde/Http/Client/Exception.php @@ -0,0 +1,41 @@ + + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Http_Client + */ + +/** + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Http_Client + */ +class Horde_Http_Client_Exception extends Exception +{ + /** + * Constructor + */ + public function __construct($message = null, $code_or_lasterror = 0) + { + if (is_array($code_or_lasterror)) { + if ($message) { + $message .= $code_or_lasterror['message']; + } else { + $message = $code_or_lasterror['message']; + } + + $this->file = $code_or_lasterror['file']; + $this->line = $code_or_lasterror['line']; + $code = $code_or_lasterror['type']; + } else { + $code = $code_or_lasterror; + } + + parent::__construct($message, $code); + } + +} diff --git a/framework/Http_Client/lib/Horde/Http/Client/Mock.php b/framework/Http_Client/lib/Horde/Http/Client/Mock.php new file mode 100644 index 000000000..e88bc6f9f --- /dev/null +++ b/framework/Http_Client/lib/Horde/Http/Client/Mock.php @@ -0,0 +1,89 @@ + + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Http_Client + */ + +/** + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Http_Client + */ +class Horde_Http_Client_Mock extends Horde_Http_Client +{ + /** + * Array of mock responses + * @var array + */ + protected $_responses = array(); + + /** + * Current mock response + * @var integer + */ + protected $_responseIndex = 0; + + /** + * Send an HTTP request + * + * @param string $method HTTP request method (GET, PUT, etc.) + * @param string $uri URI to request, if different from $this->uri + * @param mixed $data Request data. Can be an array of form data that will be + * encoded automatically, or a raw string. + * @param array $headers Any headers specific to this request. They will + * be combined with $this->_headers, and override + * headers of the same name for this request only. + * + * @return Horde_Http_Client_Response + * + * @TODO make lastRequest work somehow. + */ + public function request($method, $uri = null, $data = null, $headers = array()) + { + if ($this->_responseIndex >= count($this->_responses)) { + $this->_responseIndex = 0; + } + return $this->_responses[$this->_responseIndex++]; + } + + /** + * Set the HTTP response(s) to be returned by this adapter + * + * @param Horde_Http_Client_Response $response + */ + public function setResponse($response) + { + $this->_responses = array($response); + $this->_responseIndex = 0; + } + + /** + * Add another response to the response buffer. + * + * @param string $response + */ + public function addResponse($response) + { + $this->_responses[] = $response; + } + + /** + * Sets the position of the response buffer. Selects which + * response will be returned on the next call to read(). + * + * @param integer $index + */ + public function setResponseIndex($index) + { + if ($index < 0 || $index >= count($this->_responses)) { + throw new OutOfBoundsException; + } + $this->_responseIndex = $index; + } + +} diff --git a/framework/Http_Client/lib/Horde/Http/Client/Response.php b/framework/Http_Client/lib/Horde/Http/Client/Response.php new file mode 100644 index 000000000..808d7b615 --- /dev/null +++ b/framework/Http_Client/lib/Horde/Http/Client/Response.php @@ -0,0 +1,146 @@ + + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Http_Client + */ + +/** + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Http_Client + */ +class Horde_Http_Client_Response +{ + /** + * Fetched URI + * @var string + */ + public $uri; + + /** + * HTTP protocol version that was used + * @var float + */ + public $httpVersion; + + /** + * HTTP response code + * @var integer + */ + public $code; + + /** + * Response headers + * @var array + */ + public $headers; + + /** + * Response body + * @var stream + */ + protected $_stream; + + /** + * Constructor + */ + public function __construct($uri, $stream, $headers = array()) + { + $this->uri = $uri; + $this->_stream = $stream; + $this->_parseHeaders($headers); + } + + /** + * Parse an array of response headers, mindful of line + * continuations, etc. + * + * @param array $headers + * @return array + */ + protected function _parseHeaders($headers) + { + $lastHeader = null; + foreach ($headers as $headerLine) { + // stream_get_meta returns all headers generated while processing a + // request, including ones for redirects before an eventually successful + // request. We just want the last one, so whenever we hit a new HTTP + // header, throw out anything parsed previously and start over. + if (preg_match('/^HTTP\/(\d.\d) (\d{3})/', $headerLine, $httpMatches)) { + $this->httpVersion = $httpMatches[1]; + $this->code = (int)$httpMatches[2]; + $this->headers = array(); + $lastHeader = null; + } + + $headerLine = trim($headerLine, "\r\n"); + if ($headerLine == '') { + break; + } + if (preg_match('|^([\w-]+):\s+(.+)|', $headerLine, $m)) { + unset($lastHeader); + $headerName = strtolower($m[1]); + $headerValue = $m[2]; + + if (isset($this->headers[$headerName])) { + if (!is_array($this->headers[$headerName])) { + $this->headers[$headerName] = array($this->headers[$headerName]); + } + + $this->headers[$headerName][] = $headerValue; + } else { + $this->headers[$headerName] = $headerValue; + } + $lastHeader = $headerName; + } elseif (preg_match("|^\s+(.+)$|", $headerLine, $m) && !is_null($lastHeader)) { + if (is_array($this->headers[$lastHeader])) { + end($this->headers[$lastHeader]); + $this->headers[$lastHeader][key($this->headers[$lastHeader])] .= $m[1]; + } else { + $this->headers[$lastHeader] .= $m[1]; + } + } + } + } + + /** + * Return the body of the HTTP response. + * + * @return string HTTP response body. + */ + public function getBody() + { + $content = @stream_get_contents($this->_stream); + if ($content === false) { + throw new Horde_Http_Client_Exception('Problem reading data from ' . $this->uri . ': ' . $php_errormsg); + } + return $content; + } + + /** + * Return a stream pointing to the response body that can be used + * with all standard PHP stream functions. + */ + public function getStream() + { + return $this->_stream; + } + + /** + * Get the value of a single response header. + * + * @param string $header Header name to get ('Content-Type', 'Content-Length', etc.). This is case sensitive. + * + * @return string HTTP header value. + */ + public function getHeader($header) + { + return isset($this->headers[$header]) ? $this->headers[$header] : null; + } + +} diff --git a/framework/Http_Client/package.xml b/framework/Http_Client/package.xml new file mode 100644 index 000000000..ee48a78b8 --- /dev/null +++ b/framework/Http_Client/package.xml @@ -0,0 +1,63 @@ + + + Http_Client + pear.horde.org + Horde HTTP client libraries + This package provides a light wrapper around PHP's stream support for performing HTTP requests. + + + Chuck Hagenbuch + chuck + chuck@horde.org + yes + + 2008-09-06 + + 0.3.0 + 0.3.0 + + + beta + beta + + BSD + * Refactor the constructor and setter/getter methods to be more generic. +* Add support for proxy servers. + + + + + + + + + + + + + + + + + + + + 5.2.0 + + + 1.5.0 + + + + + + + + + + + +