Re-worked the Transport layer to support CURL and PHP Streams.

This commit is contained in:
Matt Butcher
2012-01-13 17:15:14 -06:00
parent 3ed007c5bd
commit 09fa009d6d
3 changed files with 145 additions and 29 deletions

View File

@@ -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);
}
}

View File

@@ -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);
}
/**

View File

@@ -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);
}
/**