Working on adding better file IO and a CURL transporter.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* This file contains the interface for transporters.
|
||||
*/
|
||||
|
||||
@@ -38,4 +39,38 @@ interface Transporter {
|
||||
* The string containing the request body.
|
||||
*/
|
||||
public function doRequest($uri, $method = 'GET', $headers = array(), $body = '');
|
||||
|
||||
|
||||
/**
|
||||
* Perform a request, but use a resource to read the body.
|
||||
*
|
||||
* This is a special version of the doRequest() function.
|
||||
* It handles a very spefic case where...
|
||||
*
|
||||
* - The HTTP verb requires a body (viz. PUT, POST)
|
||||
* - The body is in a resource, not a string
|
||||
*
|
||||
* Examples of appropriate cases for this variant:
|
||||
*
|
||||
* - Uploading large files.
|
||||
* - Streaming data out of a stream and into an HTTP request.
|
||||
* - Minimizing memory usage ($content strings are big).
|
||||
*
|
||||
* Note that all parameters are required.
|
||||
*
|
||||
* @todo Declare this in Transporter.
|
||||
*
|
||||
* @param string $uri
|
||||
* The target URI.
|
||||
* @param string $method
|
||||
* The method to be sent.
|
||||
* @param array $headers
|
||||
* An array of name/value header pairs.
|
||||
* @param mixed $resource
|
||||
* The string with a file path or a stream URL; or a file object resource.
|
||||
* If it is a string, then it will be opened with the default context.
|
||||
* So if you need a special context, you should open the file elsewhere
|
||||
* and pass the resource in here.
|
||||
*/
|
||||
public function doRequestWithResource($uri, $method, $headers, $resource);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user