diff --git a/src/HPCloud/Transport/CURLTransport.php b/src/HPCloud/Transport/CURLTransport.php index 743b966..9e8459e 100644 --- a/src/HPCloud/Transport/CURLTransport.php +++ b/src/HPCloud/Transport/CURLTransport.php @@ -30,6 +30,9 @@ namespace HPCloud\Transport; */ class CURLTransport implements Transporter { + + const HTTP_USER_AGENT_SUFFIX = ' (c93c0a) CURL/1.0'; + public function doRequest($uri, $method = 'GET', $headers = array(), $body = '') { $in = NULL; @@ -58,9 +61,12 @@ class CURLTransport implements Transporter { */ protected function handleDoRequest($uri, $method, $headers, $in = NULL) { + //$urlParts = parse_url($uri); + // Write to in-mem handle backed by a temp file. - $out = fopen('php://temp', 'w'); + $out = fopen('php://temp', 'wrb'); + $headerFile = fopen('php://temp', 'wr'); $curl = curl_init($uri); @@ -73,20 +79,102 @@ class CURLTransport implements Transporter { // Set the upload if (!empty($in)) { curl_setopt($curl, CURLOPT_INFILE, $in); + + // Tell CURL about the content length if we know it. + if (!empty($headers['Content-Length'])) { + curl_setopt($curl, CURLOPT_INFILESIZE, $headers['Content-Length']); + } } // Get the output. curl_setopt($curl, CURLOPT_FILE, $out); - curl_exec($curl); + // We need to capture the headers, too. + curl_setopt($curl, CURLOPT_WRITEHEADER, $headerFile); + // Show me the money! + // Results are now buffered into a tmpfile. + //curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE); + + $opts = array( + CURLOPT_USERAGENT => self::HTTP_USER_AGENT . self::HTTP_USER_AGENT_SUFFIX, + // CURLOPT_RETURNTRANSFER => TRUE, // Make curl_exec return the results. + // CURLOPT_BINARYTRANSFER => TRUE, // Raw output if RETURNTRANSFER is TRUE. + + // Put the headers in the output. + CURLOPT_HEADER => TRUE, + + // Get the final header string sent to the remote. + CURLINFO_HEADER_OUT => TRUE, + + // Timeout if the remote has not connected in 30 sec. + CURLOPT_CONNECTTIMEOUT => 30, + + // Max time to allow CURL to do the transaction. + // CURLOPT_TIMEOUT => 120, + + // If this is set, CURL will auto-deflate any encoding it can. + // CURLOPT_ENCODING => '', + + // Later, we may want to do this to support range-based + // fetching of large objects. + // CURLOPT_RANGE => 'X-Y', + + // Limit curl to only these protos. + // CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS, + + // I don't really need this, do I? + // CURLOPT_HTTP200ALIASES => array(200, 201, 202, 203, 204), + ); + + $ret = curl_exec($curl); + $info = curl_getinfo($curl); + $status = $info['http_code']; + + rewind($headerFile); + $responseHeaders = $this->fetchHeaders($headerFile); + fclose($headerFile); + + if (!$ret || $status < 200 || $status > 299) { + $err = $responseHeaders[0]; + Response::failure($status, $err, $info['url'], $method); + } + + + rewind($out); // Now we need to build a response. - // Option 1: Subclass response. - // Option 2: Build an adapter. + $resp = new Response($out, $info, $responseHeaders); curl_close($curl); + if (is_resource($in)) { + fclose($in); + } - fclose($in); + //throw new \Exception(print_r($resp, TRUE)); + + return $resp; + } + + /** + * This function reads the header file into an array. + * + * This format mataches the format returned by the stream handlers, so + * we can re-use the header parsing logic in Response. + * + * @param resource $file + * A file pointer to the file that has the headers. + * @return array + * An array of headers, one header per line. + */ + protected function fetchHeaders($file) { + $buffer = array(); + while ($header = fgets($file)) { + $header = trim($header); + if (!empty($header)) { + $buffer[] = $header; + } + } + return $buffer; } /** @@ -140,5 +228,4 @@ class CURLTransport implements Transporter { curl_setopt($curl, CURLOPT_HTTPHEADER, $buffer); } - } diff --git a/src/HPCloud/Transport/PHPStreamTransport.php b/src/HPCloud/Transport/PHPStreamTransport.php index fa1fa7e..ab699d8 100644 --- a/src/HPCloud/Transport/PHPStreamTransport.php +++ b/src/HPCloud/Transport/PHPStreamTransport.php @@ -148,27 +148,7 @@ class PHPStreamTransport implements Transporter { throw new \HPCloud\Exception($err); } - switch ($matches[1]) { - - case '403': - case '401': - throw new \HPCloud\Transport\AuthorizationException($matches[0]); - case '404': - throw new \HPCloud\Transport\FileNotFoundException($matches[0] . "($uri)"); - case '405': - throw new \HPCloud\Transport\MethodNotAllowedException($matches[0] . " ($method $uri)"); - case '409': - throw new \HPCloud\Transport\ConflictException($matches[0]); - case '412': - throw new \HPCloud\Transport\LengthRequiredException($matches[0]); - case '422': - throw new \HPCloud\Transport\UnprocessableEntityException($matches[0]); - case '500': - throw new \HPCloud\Transport\ServerException($matches[0]); - default: - throw new \HPCloud\Exception($matches[0]); - - } + Response::failure($matches[1], $matches[0], $uri, $method); } /** diff --git a/src/HPCloud/Transport/Response.php b/src/HPCloud/Transport/Response.php index 7ab3174..0026843 100644 --- a/src/HPCloud/Transport/Response.php +++ b/src/HPCloud/Transport/Response.php @@ -36,16 +36,65 @@ class Response { protected $metadata; protected $headers; + /** + * Handle error response. + * + * When a response is a failure, it should pass through this function, + * which generates the appropriate exception and then throws it. + * + * @param int $code + * The HTTP status code, e.g. 404, 500. + * @param string $err + * The error string, as bubbled up. + * @param string $uri + * The URI. + * @param string $method + * The HTTP method, e.g. 'HEAD', 'GET', 'DELETE'. + * @param string $extra + * An extra string of debugging information. (NOT USED) + * @throws \HPCloud\Exception + * A wide variety of \HPCloud\Transport exceptions. + */ + public static function failure($code, $err = 'Unknown', $uri = '', $method = '', $extra = '') { + switch ($code) { + + case '403': + case '401': + throw new \HPCloud\Transport\AuthorizationException($err); + case '404': + throw new \HPCloud\Transport\FileNotFoundException($err . " ($uri)"); + case '405': + throw new \HPCloud\Transport\MethodNotAllowedException($err . " ($method $uri)"); + case '409': + throw new \HPCloud\Transport\ConflictException($err); + case '412': + throw new \HPCloud\Transport\LengthRequiredException($err); + case '422': + throw new \HPCloud\Transport\UnprocessableEntityException($err); + case '500': + throw new \HPCloud\Transport\ServerException($err); + default: + throw new \HPCloud\Exception($err); + + } + + } + /** * Construct a new Response. * * The Transporter implementations use this to * construct a response. */ - public function __construct($handle, $metadata) { + public function __construct($handle, $metadata, $headers = NULL) { $this->handle = $handle; $this->metadata = $metadata; - $this->headers = $this->parseHeaders($metadata['wrapper_data']); + + if (!isset($headers) && isset($metadata['wrapper_data'])) { + $headers = $metadata['wrapper_data']; + } + + $this->headers = $this->parseHeaders($headers); } /**