Large overhaul to allow two forms of object fetching.

This commit is contained in:
Matt Butcher
2012-01-06 17:06:31 -06:00
parent 8560d90e5f
commit d7731832fc
4 changed files with 193 additions and 6 deletions

View File

@@ -243,6 +243,82 @@ class Container implements \Countable, \IteratorAggregate {
return TRUE;
}
/**
* Get the object with the given name.
*
* This fetches a single object with the given name.
*
* This does not yet support the following features of Swift:
*
* - Byte range queries.
* - If-Modified-Since/If-Unmodified-Since
* - If-Match/If-None-Match
*
*/
public function object($name) {
$headers = array();
// Auth token.
$headers['X-Auth-Token'] = $this->token;
$client = \HPCloud\Transport::instance();
$response = $client->doRequest($url, 'GET', $headers);
if ($response->status() != 200) {
throw new \HPCloud\Exception('An unknown error occurred while saving: ' . $response->status());
}
}
/**
* Fetch an object, but delay fetching its contents.
*
* This retrieves all of the information about an object except for
* its contents. Size, hash, metadata, and modification date
* information are all retrieved and wrapped.
*
* The data comes back as a RemoteObject, which can be used to
* transparently fetch the object's content, too.
*
* Why Use This?
*
* The regular object() call will fetch an entire object, including
* its content. This may not be desireable for cases where the object
* is large.
*
* This method can featch the relevant metadata, but delay fetching
* the content until it is actually needed.
*
* Since RemoteObject extends Object, all of the calls that can be
* made to an Object can also be made to a RemoteObject. Be aware,
* though, that calling RemoteObject::content() will initiate another
* network operation.
*
* @param string $name
* The name of the object to fetch.
* @return \HPCloud\Storage\ObjectStorage\RemoteObject
* A remote object ready for use.
*/
public function remoteObject($name) {
$url = $this->url . '/' . urlencode($name);
$headers = array(
'X-Auth-Token' => $this->token,
);
$client = \HPCloud\Transport::instance();
$response = $client->doRequest($url, 'HEAD', $headers);
if ($response->status() != 200) {
throw new \HPCloud\Exception('An unknown error occurred while saving: ' . $response->status());
}
$headers = $response->headers();
$objectUrl = $this->url . '/' . urlencode($name);
return RemoteObject::newFromHeaders($name, $headers, $this->token, $objectUrl);
}
/**
* Get a list of objects in this container.
*
@@ -284,7 +360,7 @@ class Container implements \Countable, \IteratorAggregate {
* the directory name as a "prefix". This will return only objects
* that begin with that prefix.
*
* (Directory-like behavior is also supported by using "directory
* (Directory-like behavior is also supported by using "directory
* markers". See objectsWithPath().)
*
* Prefixes
@@ -341,12 +417,12 @@ class Container implements \Countable, \IteratorAggregate {
/**
* Specify a path (subdirectory) to traverse.
*
* OpenStack Swift provides two basic ways to handle directory-like
* structures. The first is using a prefix (see objectsByPrefix()).
* OpenStack Swift provides two basic ways to handle directory-like
* structures. The first is using a prefix (see objectsByPrefix()).
* The second is to create directory markers and use a path.
*
* A directory marker is just a file with a name that is
* directory-like. You create it exactly as you create any other file.
* A directory marker is just a file with a name that is
* directory-like. You create it exactly as you create any other file.
* Typically, it is 0 bytes long.
*
* @code

View File

@@ -93,7 +93,10 @@ class Object {
* OpenStack allows you to specify metadata for a file. Metadata items
* must follow these conventions:
*
* - names must contain only letters, numbers, and short dashes.
* - names must contain only letters, numbers, and short dashes. Since
* OpenStack normalizes the name to begin with uppercase, it is
* suggested that you follow this convetion: Foo, not foo. Or you
* can do your own normalizing (such as converting all to lowercase.
* - values must be encoded if they contain newlines or binary data.
* While the exact encoding is up to you, Base-64 encoding is probably
* your best bet.
@@ -101,6 +104,11 @@ class Object {
* This library does only minimal processing of metadata, and does no
* error checking, escaping, etc. This is up to the implementor.
*
* IMPORTANT: Current versions of OpenStack Swift see the names FOO,
* Foo, foo, and fOo as the same. This is not to say that it is case
* insensitive; only that it normalizes strings according to its own
* rules.
*
* @param array $array
* An associative array of metadata names to values.
*/

View File

@@ -32,9 +32,80 @@ class RemoteObject extends Object {
$object->etag = (string) $data['hash'];
$object->lastModified = strtotime($data['last_modified']);
$object->token = $token;
$object->url = $url;
return $object;
}
/**
* Create a new RemoteObject from HTTP headers.
*
* This is used to create objects from GET and HEAD requests, which
* return all of the metadata inside of the headers.
*
* @param string $name
* The name of the object.
* @param array $headers
* An associative array of HTTP headers in the exact format
* documented by OpenStack's API docs.
* @param string $token
* The current auth token (used for issuing subsequent requests).
* @param string $url
* The URL to the object in the object storage. Used for issuing
* subsequent requests.
*/
public static function newFromHeaders($name, $headers, $token, $url) {
$object = new RemoteObject($name);
//throw new \Exception(print_r($headers, TRUE));
$object->setContentType($headers['Content-Type']);
$object->contentLength = (int) $headers['Content-Length'];
$object->etag = (string) $headers['Etag']; // ETag is now Etag.
$object->lastModified = strtotime($headers['Last-Modified']);
// Set the metadata, too.
$object->setMetadata(self::extractHeaderAttributes($headers));
$object->token = $token;
$object->url = $url;
return $object;
}
/**
* Extract object attributes from HTTP headers.
*
* When OpenStack sends object attributes, it sometimes embeds them in
* HTTP headers with a prefix. This function parses the headers and
* returns the attributes as name/value pairs.
*
* Note that no decoding (other than the minimum amount necessary) is
* done to the attribute names or values. The Open Stack Swift
* documentation does not prescribe encoding standards for name or
* value data, so it is left up to implementors to choose their own
* strategy.
*
* @param array $headers
* An associative array of HTTP headers.
* @return array
* An associative array of name/value attribute pairs.
*/
public static function extractHeaderAttributes($headers) {
$attributes = array();
$offset = strlen(Container::METADATA_HEADER_PREFIX);
foreach ($headers as $header => $value) {
$index = strpos($header, Container::METADATA_HEADER_PREFIX);
if ($index === 0) {
$key = substr($header, $offset);
$attributes[$key] = $value;
}
}
return $attributes;
}
public function contentLength() {
if (!empty($this->content)) {
return parent::contentLength();

View File

@@ -110,6 +110,8 @@ class ContainerTest extends \HPCloud\Tests\TestCase {
$obj = new Object(self::FNAME, self::FCONTENT, self::FTYPE);
$obj->setMetadata(array('foo' => '1234'));
$this->assertEquals(self::FCONTENT, $obj->content());
try {
$ret = $container->save($obj);
}
@@ -121,6 +123,36 @@ class ContainerTest extends \HPCloud\Tests\TestCase {
$this->assertTrue($ret);
}
/**
* @depends testSave
*/
public function testRemoteObject() {
$container = $this->containerFixture();
$object = $container->remoteObject(self::FNAME);
$this->assertEquals(self::FNAME, $object->name());
$this->assertEquals(self::FTYPE, $object->contentType());
$etag = md5(self::FCONTENT);
$this->assertEquals($etag, $object->eTag());
$md = $object->metadata();
$this->assertEquals(1, count($md));
// Note that headers are normalized remotely to have initial
// caps. Since we have no way of knowing what the original
// metadata casing is, we leave it with initial caps.
$this->assertEquals('1234', $md['Foo']);
$content = $object->content();
$this->assertEquals(self::FCONTENT, $content);
// Overwrite the copy:
$object->setContent('HI');
$this->assertEquals('HI', $object->content());
}
/**
* @depends testSave
*/