From b22fc5b74aecd505d4e2df01258171fc65d312cf Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 2 Nov 2009 06:56:31 -0800 Subject: Revert "Rebuilt HTTPClient class as an extension of PEAR HTTP_Request2 package, adding redirect handling and convenience functions." Going to restructure a little more before finalizing this... This reverts commit fa37967858c3c29000797e510e5f98aca8ab558f. --- lib/Shorturl_api.php | 23 ++++--- lib/curlclient.php | 179 ++++++++++++++++++++++++++++++++++++++++++++++++++ lib/default.php | 2 + lib/httpclient.php | 180 ++++++++++++--------------------------------------- lib/oauthclient.php | 65 ++++++++++--------- lib/ping.php | 14 ++-- lib/snapshot.php | 21 +++++- 7 files changed, 297 insertions(+), 187 deletions(-) create mode 100644 lib/curlclient.php (limited to 'lib') diff --git a/lib/Shorturl_api.php b/lib/Shorturl_api.php index ef0d8dda4..18ae7719b 100644 --- a/lib/Shorturl_api.php +++ b/lib/Shorturl_api.php @@ -41,17 +41,22 @@ abstract class ShortUrlApi return strlen($url) >= common_config('site', 'shorturllength'); } - protected function http_post($data) - { - $request = new HTTPClient($this->service_url); - return $request->post($data); + protected function http_post($data) { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $this->service_url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + $response = curl_exec($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + if (($code < 200) || ($code >= 400)) return false; + return $response; } - protected function http_get($url) - { - $service = $this->service_url . urlencode($url); - $request = new HTTPClient($service); - return $request->get(); + protected function http_get($url) { + $encoded_url = urlencode($url); + return file_get_contents("{$this->service_url}$encoded_url"); } protected function tidy($response) { diff --git a/lib/curlclient.php b/lib/curlclient.php new file mode 100644 index 000000000..c307c2984 --- /dev/null +++ b/lib/curlclient.php @@ -0,0 +1,179 @@ +. + * + * @category HTTP + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +define(CURLCLIENT_VERSION, "0.1"); + +/** + * Wrapper for Curl + * + * Makes Curl HTTP client calls within our HTTPClient framework + * + * @category HTTP + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class CurlClient extends HTTPClient +{ + function __construct() + { + } + + function head($url, $headers=null) + { + $ch = curl_init($url); + + $this->setup($ch); + + curl_setopt_array($ch, + array(CURLOPT_NOBODY => true)); + + if (!is_null($headers)) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + } + + $result = curl_exec($ch); + + curl_close($ch); + + return $this->parseResults($result); + } + + function get($url, $headers=null) + { + $ch = curl_init($url); + + $this->setup($ch); + + if (!is_null($headers)) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + } + + $result = curl_exec($ch); + + curl_close($ch); + + return $this->parseResults($result); + } + + function post($url, $headers=null, $body=null) + { + $ch = curl_init($url); + + $this->setup($ch); + + curl_setopt($ch, CURLOPT_POST, true); + + if (!is_null($body)) { + curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + } + + if (!is_null($headers)) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + } + + $result = curl_exec($ch); + + curl_close($ch); + + return $this->parseResults($result); + } + + function setup($ch) + { + curl_setopt_array($ch, + array(CURLOPT_USERAGENT => $this->userAgent(), + CURLOPT_HEADER => true, + CURLOPT_RETURNTRANSFER => true)); + } + + function userAgent() + { + $version = curl_version(); + return parent::userAgent() . " CurlClient/".CURLCLIENT_VERSION . " cURL/" . $version['version']; + } + + function parseResults($results) + { + $resp = new HTTPResponse(); + + $lines = explode("\r\n", $results); + + if (preg_match("#^HTTP/1.[01] (\d\d\d) .+$#", $lines[0], $match)) { + $resp->code = $match[1]; + } else { + throw Exception("Bad format: initial line is not HTTP status line"); + } + + $lastk = null; + + for ($i = 1; $i < count($lines); $i++) { + $l =& $lines[$i]; + if (mb_strlen($l) == 0) { + $resp->body = implode("\r\n", array_slice($lines, $i + 1)); + break; + } + if (preg_match("#^(\S+):\s+(.*)$#", $l, $match)) { + $k = $match[1]; + $v = $match[2]; + + if (array_key_exists($k, $resp->headers)) { + if (is_array($resp->headers[$k])) { + $resp->headers[$k][] = $v; + } else { + $resp->headers[$k] = array($resp->headers[$k], $v); + } + } else { + $resp->headers[$k] = $v; + } + $lastk = $k; + } else if (preg_match("#^\s+(.*)$#", $l, $match)) { + // continuation line + if (is_null($lastk)) { + throw Exception("Bad format: initial whitespace in headers"); + } + $h =& $resp->headers[$lastk]; + if (is_array($h)) { + $n = count($h); + $h[$n-1] .= $match[1]; + } else { + $h .= $match[1]; + } + } + } + + return $resp; + } +} diff --git a/lib/default.php b/lib/default.php index f6cc4b725..7ec8558b0 100644 --- a/lib/default.php +++ b/lib/default.php @@ -228,6 +228,8 @@ $default = array('contentlimit' => null), 'message' => array('contentlimit' => null), + 'http' => + array('client' => 'curl'), // XXX: should this be the default? 'location' => array('namespace' => 1), // 1 = geonames, 2 = Yahoo Where on Earth ); diff --git a/lib/httpclient.php b/lib/httpclient.php index ee894e983..f16e31e10 100644 --- a/lib/httpclient.php +++ b/lib/httpclient.php @@ -31,9 +31,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once 'HTTP/Request2.php'; -require_once 'HTTP/Request2/Response.php'; - /** * Useful structure for HTTP responses * @@ -41,42 +38,18 @@ require_once 'HTTP/Request2/Response.php'; * ways of doing them. This class hides the specifics of what underlying * library (curl or PHP-HTTP or whatever) that's used. * - * This extends the HTTP_Request2_Response class with methods to get info - * about any followed redirects. - * * @category HTTP - * @package StatusNet - * @author Evan Prodromou - * @author Brion Vibber - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ */ -class HTTPResponse extends HTTP_Request2_Response -{ - function __construct(HTTP_Request2_Response $response, $url, $redirects=0) - { - foreach (get_object_vars($response) as $key => $val) { - $this->$key = $val; - } - $this->url = strval($url); - $this->redirectCount = intval($redirects); - } - - /** - * Get the count of redirects that have been followed, if any. - * @return int - */ - function getRedirectCount() { - return $this->redirectCount; - } - /** - * Gets the final target URL, after any redirects have been followed. - * @return string URL - */ - function getUrl() { - return $this->url; - } +class HTTPResponse +{ + public $code = null; + public $headers = array(); + public $body = null; } /** @@ -86,133 +59,64 @@ class HTTPResponse extends HTTP_Request2_Response * ways of doing them. This class hides the specifics of what underlying * library (curl or PHP-HTTP or whatever) that's used. * - * This extends the PEAR HTTP_Request2 package: - * - sends StatusNet-specific User-Agent header - * - 'follow_redirects' config option, defaulting off - * - 'max_redirs' config option, defaulting to 10 - * - extended response class adds getRedirectCount() and getUrl() methods - * - get() and post() convenience functions return body content directly - * * @category HTTP * @package StatusNet * @author Evan Prodromou - * @author Brion Vibber * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ -class HTTPClient extends HTTP_Request2 +class HTTPClient { + static $_client = null; - function __construct($url=null, $method=self::METHOD_GET, $config=array()) + static function start() { - $this->config['max_redirs'] = 10; - $this->config['follow_redirects'] = false; - parent::__construct($url, $method, $config); - $this->setHeader('User-Agent', $this->userAgent()); + if (!is_null(self::$_client)) { + return self::$_client; + } + + $type = common_config('http', 'client'); + + switch ($type) { + case 'curl': + self::$_client = new CurlClient(); + break; + default: + throw new Exception("Unknown HTTP client type '$type'"); + break; + } + + return self::$_client; } - /** - * Convenience function to run a get request and return the response body. - * Use when you don't need to get into details of the response. - * - * @return mixed string on success, false on failure - */ - function get() + function head($url, $headers) { - $this->setMethod(self::METHOD_GET); - return $this->doRequest(); + throw new Exception("HEAD method unimplemented"); } - /** - * Convenience function to post form data and return the response body. - * Use when you don't need to get into details of the response. - * - * @param array associative array of form data to submit - * @return mixed string on success, false on failure - */ - public function post($data=array()) + function get($url, $headers) { - $this->setMethod(self::METHOD_POST); - if ($data) { - $this->addPostParameter($data); - } - return $this->doRequest(); + throw new Exception("GET method unimplemented"); } - /** - * @return mixed string on success, false on failure - */ - protected function doRequest() + function post($url, $headers, $body) { - try { - $response = $this->send(); - $code = $response->getStatus(); - if (($code < 200) || ($code >= 400)) { - return false; - } - return $response->getBody(); - } catch (HTTP_Request2_Exception $e) { - $this->log(LOG_ERR, $e->getMessage()); - return false; - } + throw new Exception("POST method unimplemented"); } - - protected function log($level, $detail) { - $method = $this->getMethod(); - $url = $this->getUrl(); - common_log($level, __CLASS__ . ": HTTP $method $url - $detail"); + + function put($url, $headers, $body) + { + throw new Exception("PUT method unimplemented"); } - /** - * Pulls up StatusNet's customized user-agent string, so services - * we hit can track down the responsible software. - */ - function userAgent() + function delete($url, $headers) { - return "StatusNet/".STATUSNET_VERSION." (".STATUSNET_CODENAME.")"; + throw new Exception("DELETE method unimplemented"); } - function send() + function userAgent() { - $maxRedirs = intval($this->config['max_redirs']); - if (empty($this->config['follow_redirects'])) { - $maxRedirs = 0; - } - $redirs = 0; - do { - try { - $response = parent::send(); - } catch (HTTP_Request2_Exception $e) { - $this->log(LOG_ERR, $e->getMessage()); - throw $e; - } - $code = $response->getStatus(); - if ($code >= 200 && $code < 300) { - $reason = $response->getReasonPhrase(); - $this->log(LOG_INFO, "$code $reason"); - } elseif ($code >= 300 && $code < 400) { - $url = $this->getUrl(); - $target = $response->getHeader('Location'); - - if (++$redirs >= $maxRedirs) { - common_log(LOG_ERR, __CLASS__ . ": Too many redirects: skipping $code redirect from $url to $target"); - break; - } - try { - $this->setUrl($target); - $this->setHeader('Referer', $url); - common_log(LOG_INFO, __CLASS__ . ": Following $code redirect from $url to $target"); - continue; - } catch (HTTP_Request2_Exception $e) { - common_log(LOG_ERR, __CLASS__ . ": Invalid $code redirect from $url to $target"); - } - } else { - $reason = $response->getReasonPhrase(); - $this->log(LOG_ERR, "$code $reason"); - } - break; - } while ($maxRedirs); - return new HTTPResponse($response, $this->getUrl(), $redirs); + return "StatusNet/".STATUSNET_VERSION." (".STATUSNET_CODENAME.")"; } } diff --git a/lib/oauthclient.php b/lib/oauthclient.php index 1a86e2460..f1827726e 100644 --- a/lib/oauthclient.php +++ b/lib/oauthclient.php @@ -43,7 +43,7 @@ require_once 'OAuth.php'; * @link http://status.net/ * */ -class OAuthClientException extends Exception +class OAuthClientCurlException extends Exception { } @@ -97,14 +97,9 @@ class OAuthClient function getRequestToken($url) { $response = $this->oAuthGet($url); - $arr = array(); - parse_str($response, $arr); - if (isset($arr['oauth_token']) && isset($arr['oauth_token_secret'])) { - $token = new OAuthToken($arr['oauth_token'], @$arr['oauth_token_secret']); - return $token; - } else { - throw new OAuthClientException(); - } + parse_str($response); + $token = new OAuthToken($oauth_token, $oauth_token_secret); + return $token; } /** @@ -182,7 +177,7 @@ class OAuthClient } /** - * Make a HTTP request. + * Make a HTTP request using cURL. * * @param string $url Where to make the * @param array $params post parameters @@ -191,32 +186,40 @@ class OAuthClient */ function httpRequest($url, $params = null) { - $request = new HTTPClient($url); - $request->setConfig(array( - 'connect_timeout' => 120, - 'timeout' => 120, - 'follow_redirects' => true, - 'ssl_verify_peer' => false, - )); - - // Twitter is strict about accepting invalid "Expect" headers - $request->setHeader('Expect', ''); + $options = array( + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FAILONERROR => true, + CURLOPT_HEADER => false, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_USERAGENT => 'StatusNet', + CURLOPT_CONNECTTIMEOUT => 120, + CURLOPT_TIMEOUT => 120, + CURLOPT_HTTPAUTH => CURLAUTH_ANY, + CURLOPT_SSL_VERIFYPEER => false, + + // Twitter is strict about accepting invalid "Expect" headers + + CURLOPT_HTTPHEADER => array('Expect:') + ); if (isset($params)) { - $request->setMethod(HTTP_Request2::METHOD_POST); - $request->setBody($params); + $options[CURLOPT_POST] = true; + $options[CURLOPT_POSTFIELDS] = $params; } - try { - $response = $request->send(); - $code = $response->getStatus(); - if ($code < 200 || $code >= 400) { - throw new OAuthClientException($response->getBody(), $code); - } - return $response->getBody(); - } catch (Exception $e) { - throw new OAuthClientException($e->getMessage(), $e->getCode()); + $ch = curl_init($url); + curl_setopt_array($ch, $options); + $response = curl_exec($ch); + + if ($response === false) { + $msg = curl_error($ch); + $code = curl_errno($ch); + throw new OAuthClientCurlException($msg, $code); } + + curl_close($ch); + + return $response; } } diff --git a/lib/ping.php b/lib/ping.php index 2797c1b2d..175bf8440 100644 --- a/lib/ping.php +++ b/lib/ping.php @@ -44,18 +44,20 @@ function ping_broadcast_notice($notice) { array('nickname' => $profile->nickname)), $tags)); - $request = new HTTPClient($notify_url, HTTP_Request2::METHOD_POST); - $request->setHeader('Content-Type', 'text/xml'); - $request->setBody($req); - $httpResponse = $request->send(); + $context = stream_context_create(array('http' => array('method' => "POST", + 'header' => + "Content-Type: text/xml\r\n". + "User-Agent: StatusNet/".STATUSNET_VERSION."\r\n", + 'content' => $req))); + $file = file_get_contents($notify_url, false, $context); - if (!$httpResponse || mb_strlen($httpResponse->getBody()) == 0) { + if ($file === false || mb_strlen($file) == 0) { common_log(LOG_WARNING, "XML-RPC empty results for ping ($notify_url, $notice->id) "); continue; } - $response = xmlrpc_decode($httpResponse->getBody()); + $response = xmlrpc_decode($file); if (is_array($response) && xmlrpc_is_fault($response)) { common_log(LOG_WARNING, diff --git a/lib/snapshot.php b/lib/snapshot.php index 6829e8a75..ede846e5b 100644 --- a/lib/snapshot.php +++ b/lib/snapshot.php @@ -172,11 +172,26 @@ class Snapshot { // XXX: Use OICU2 and OAuth to make authorized requests + $postdata = http_build_query($this->stats); + + $opts = + array('http' => + array( + 'method' => 'POST', + 'header' => 'Content-type: '. + 'application/x-www-form-urlencoded', + 'content' => $postdata, + 'user_agent' => 'StatusNet/'.STATUSNET_VERSION + ) + ); + + $context = stream_context_create($opts); + $reporturl = common_config('snapshot', 'reporturl'); - $request = new HTTPClient($reporturl, HTTP_Request2::METHOD_POST); - $request->addPostParameter($this->stats); - $request->send(); + $result = @file_get_contents($reporturl, false, $context); + + return $result; } /** -- cgit v1.2.3-54-g00ecf