diff --git a/src/HPCloud/Transport/CURLTransport.php b/src/HPCloud/Transport/CURLTransport.php index 9218bb3..743b966 100644 --- a/src/HPCloud/Transport/CURLTransport.php +++ b/src/HPCloud/Transport/CURLTransport.php @@ -8,11 +8,137 @@ namespace HPCloud\Transport; /** * Provide HTTP transport with CURL. + * + * You should choose the Curl backend if... + * + * - You KNOW Curl support is compiled into your PHP version + * - You do not like the built-in PHP HTTP handler + * - Performance is a big deal to you + * - You will be sending large objects (>2M) + * - Or PHP stream wrappers for URLs are not supported on your system. + * + * CURL is demonstrably faster than the built-in PHP HTTP handling, so + * ths library gives a performance boost. Error reporting is slightly + * better too. + * + * But the real strong point to Curl is that it can take file objects + * and send them over HTTP without having to buffer them into strings + * first. This saves memory and processing. + * + * The only downside to Curl is that it is not available on all hosts. + * Some installations of PHP do not compile support. */ class CURLTransport implements Transporter { public function doRequest($uri, $method = 'GET', $headers = array(), $body = '') { + $in = NULL; + if (!empty($body)) { + // First we turn our body into a temp-backed buffer. + $in = fopen('php://temp', 'wr', FALSE); + fwrite($in, $body, strlen($body)); + rewind($in); + } + return $this->handleDoRequest($uri, $method, $headers, $in); + + } + + public function doRequestWithResource($uri, $method, $headers, $resource) { + if (is_string($resource)) { + $in = open($resource, 'rb', FALSE); + } + else { + $in = $resource; + } + return $this->handleDoRequest($uri, $method, $headers, $resource); + } + + /** + * Internal workhorse. + */ + protected function handleDoRequest($uri, $method, $headers, $in = NULL) { + + + // Write to in-mem handle backed by a temp file. + $out = fopen('php://temp', 'w'); + + $curl = curl_init($uri); + + // Set method + $this->determineMethod($curl, $method); + + // Set headers + $this->setHeaders($curl, $headers); + + // Set the upload + if (!empty($in)) { + curl_setopt($curl, CURLOPT_INFILE, $in); + } + + // Get the output. + curl_setopt($curl, CURLOPT_FILE, $out); + + curl_exec($curl); + + // Now we need to build a response. + // Option 1: Subclass response. + // Option 2: Build an adapter. + + curl_close($curl); + + fclose($in); + } + + /** + * Set the appropriate constant on the CURL object. + * + * Curl handles method name setting in a slightly counter-intuitive + * way, so we have a special function for setting the method + * correctly. Note that since we do not POST as www-form-*, we + * use a custom post. + * + * @param resource $curl + * A curl object. + * @param string $method + * An HTTP method name. + */ + protected function determineMethod($curl, $method) { + $method = strtoupper($method); + + switch ($method) { + case 'GET': + curl_setopt($curl, CURLOPT_HTTPGET, TRUE); + break; + case 'HEAD': + curl_setopt($curl, CURLOPT_NOBODY, TRUE); + break; + + // Put is problematic: Some PUT requests might not have + // a body. + case 'PUT': + curl_setopt($curl, CURLOPT_PUT, TRUE); + break; + + // We use customrequest for post because we are + // not submitting form data. + case 'POST': + case 'DELETE': + case 'COPY': + default: + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, TRUE); + } + + } + + public function setHeaders($curl, $headers) { + $buffer = array(); + $format = '%s: %s'; + + foreach ($headers as $name => $value) { + $buffer[] = sprintf($format, $name, $value); + } + + curl_setopt($curl, CURLOPT_HTTPHEADER, $buffer); } } diff --git a/src/HPCloud/Transport/PHPStreamTransport.php b/src/HPCloud/Transport/PHPStreamTransport.php index 127860f..fa1fa7e 100644 --- a/src/HPCloud/Transport/PHPStreamTransport.php +++ b/src/HPCloud/Transport/PHPStreamTransport.php @@ -73,6 +73,60 @@ class PHPStreamTransport implements Transporter { return $response; } + /** + * Implements Transporter::doRequestWithResource(). + * + * Unfortunately, PHP Stream Wrappers do not allow HTTP data to be read + * out of a file resource, so using this method will allow some + * performance improvement (because grabage collection can collect faster), + * but not a lot. + * + * While PHP's underlying architecture should still adequately buffer large + * strings, the effects of this buffering on really large data (5G or so) + * is unknown. + */ + public function doRequestWithResource($uri, $method, $headers, $resource) { + + + // In a PHP stream there is no way to buffer content for sending. + // XXX: Could we create a class with a __tostring that read data in piecemeal? + // That wouldn't solve the problem, but it might minimize damage. + if (is_string($resource)) { + $in = fopen($resource, 'rb', FALSE); + } + else { + $in = $resource; + } + $body = ''; + while (!feof($in)) { + $body .= fread($in, 8192); + } + + $cxt = $this->buildStreamContext($method, $headers, $body); + $res = @fopen($uri, 'rb', FALSE, $cxt); + + // If there is an error, we try to react + // intelligently. + if ($res === FALSE) { + $err = error_get_last(); + + if (empty($err['message'])) { + throw new \HPCloud\Exception("An unknown exception occurred while sending a request."); + } + $this->guessError($err['message'], $uri, $method); + + // Should not get here. + return; + } + + $metadata = stream_get_meta_data($res); + + $response = new Response($res, $metadata); + + return $response; + + } + /** * Given an error, this tries to guess the cause and throw an exception. * diff --git a/src/HPCloud/Transport/Transporter.php b/src/HPCloud/Transport/Transporter.php index dfd70a2..5e4f45f 100644 --- a/src/HPCloud/Transport/Transporter.php +++ b/src/HPCloud/Transport/Transporter.php @@ -1,5 +1,6 @@