Story #1057, #1058, #1059: As a user I can set content disposition.

As a user I can set content encoding.
As a user I can set CORS headers.

All of these turned out to be slight variations on the same operations.
This commit is contained in:
Matt Butcher
2012-01-12 16:58:56 -06:00
parent 3020099ecf
commit 1ec2d93691
4 changed files with 241 additions and 2 deletions

View File

@@ -358,9 +358,27 @@ class Container implements \Countable, \IteratorAggregate {
$headers['Content-Length'] = $obj->contentLength();
}
// Add content encoding, if necessary.
$encoding = $obj->encoding();
if (!empty($encoding)) {
$headers['Content-Encoding'] = urlencode($encoding);
}
// Add content disposition, if necessary.
$disposition = $obj->disposition();
if (!empty($disposition)) {
$headers['Content-Disposition'] = $disposition;
}
// Auth token.
$headers['X-Auth-Token'] = $this->token;
// Add any custom headers:
$moreHeaders = $obj->additionalHeaders();
if (!empty($moreHeaders)) {
$headers += $moreHeaders;
}
$client = \HPCloud\Transport::instance();
$response = $client->doRequest($url, 'PUT', $headers, $obj->content());
@@ -378,6 +396,10 @@ class Container implements \Countable, \IteratorAggregate {
* else. This is a convenient way to set additional metadata without
* having to re-upload a potentially large object.
*
* Swift's behavior during this operation is sometimes unpredictable,
* particularly in cases where custom headers have been set.
* Use with caution.
*
* @param \HPCloud\Storage\ObjectStorage\Object $obj
* The object to update.
*

View File

@@ -63,6 +63,14 @@ class Object {
*/
protected $metadata = array();
protected $contentEncoding;
protected $contentDisposition;
/**
* Extension mechanism for new headers.
*/
protected $additionalHeaders = array();
/**
* Construct a new object for storage.
@@ -302,6 +310,126 @@ class Object {
return md5($this->content);
}
/**
* Set the encoding for a file.
*
* You can use content encoding on compressed content to indicate to
* the receiving agent that a file is encoded using a specific
* compression type.
*
* Typical compression types are 'gzip', 'zip', and 'compress', though
* many others exist.
*
* This allows you, for example, to save a zipped file, yet preserve
* its underlying content type. For example, for a gzipped text/plain
* file, you can set the content type to "text/plain" and the encoding
* to "gzip". This allows many user agents to receive the compressed
* data and automatically decompress them and display them correctly.
*
* @param string $encoding
* A valid encoding type.
*/
public function setEncoding($encoding) {
$this->contentEncoding = $encoding;
}
/**
* Get the encoding (if any) for this object.
*
* Encoding is used to indicate how a file was encoded or compressed.
* See setEncoding() for more information.
*
* @return string
* The encoding type.
*/
public function encoding() {
return $this->contentEncoding;
}
/**
* Set the content disposition.
*
* This makes it possible to have the file act like a download (in a
* browser or similar agent), even if the MIME type normally triggers
* a display.
*
* The typical value for this is:
* @code
* <?php
* $object->setDisposition('attachment; filename=foo.png');
* ?>
* @endcode
*
* A disposition string should not include any newline characters or
* binary data.
*
* @param string $disposition
* A valid disposition declaration. These are defined in various
* HTTP specifications.
*/
public function setDisposition($disposition) {
$this->contentDisposition = $disposition;
}
/**
* Get the current disposition string, if any.
*
* See setDisposition() for discussion.
*
* @return string
* The disposition string, or NULL if none is set.
*/
public function disposition() {
return $this->contentDisposition;
}
/**
* Set additional headers for storage.
*
* EXPERT.
*
* Headers set here will be added to the HTTP request during save
* operations. They are not merged into existing headers until
* save-time.
*
* This provides a mechanism for adding extension headers. CORS
* headers and possibly others are stored by Swift, but have no
* semantic value to Swift or to popular user agents.
*
* There are a few things to note about this mechanism:
*
* - Existing headers cannot be overwritten. Only new headers can be
* added.
* - Headers are not merged. They are simply sent to the remote
* server. A new object must be retrieved from the server before
* these headers will be accessible.
* - Swift only stores certain headers. If you supply an unrecognized
* header to Swift, it may simply ignore it.
* - The RemoteObject::headers() method provides access to all of the
* headers returned from Swift.
* - Headers are merged in as they are, with no cleaning, encoding, or
* checking. You must ensure that the headers are in the proper
* format.
*
* @param array $headers
* An associative array where each name is an HTTP header name, and
* each value is the HTTP header value. No encoding or escaping is
* done.
*/
public function setAdditionalHeaders($headers) {
$this->additionalHeaders = $headers;
}
/**
* Return additional headers.
*
* Headers here have likely not been stored remotely until
* Container::save() is called on the object.
*/
public function additionalHeaders() {
return $this->additionalHeaders;
}
/**
* This object should be transmitted in chunks.
*

View File

@@ -35,6 +35,17 @@ class RemoteObject extends Object {
protected $contentVerification = TRUE;
protected $caching = FALSE;
/**
* All headers received from a remote are stored in this array.
* Implementing subclasses can access this array for complete access
* to the HTTP headers.
*
* This will be empty if the object was constructed from JSON, and may
* serve as a good indicator that the object does not have all
* attributes set.
*/
protected $allHeaders;
/**
* Create a new RemoteObject from JSON data.
*
@@ -57,6 +68,9 @@ class RemoteObject extends Object {
$object->token = $token;
$object->url = $url;
// FIXME: What do we do about HTTP header data that doesn't come
// back in JSON?
return $object;
}
@@ -80,6 +94,8 @@ class RemoteObject extends Object {
public static function newFromHeaders($name, $headers, $token, $url) {
$object = new RemoteObject($name);
$object->allHeaders = $headers;
//throw new \Exception(print_r($headers, TRUE));
$object->setContentType($headers['Content-Type']);
@@ -90,6 +106,17 @@ class RemoteObject extends Object {
// Set the metadata, too.
$object->setMetadata(Container::extractHeaderAttributes($headers));
// If content encoding and disposition exist, set them on the
// object.
if (!empty($headers['Content-Disposition'])) {
$object->setDisposition($headers['Content-Disposition']);
}
if (!empty($headers['Content-Encoding'])) {
$object->setEncoding($headers['Content-Encoding']);
}
$object->token = $token;
$object->url = $url;
@@ -138,6 +165,21 @@ class RemoteObject extends Object {
return $this->metadata;
}
/**
* Get the HTTP headers sent by the server.
*
* EXPERT.
*
* This returns the array of minimally processed HTTP headers that
* were sent from the server.
*
* @return array
* An associative array of header names and values.
*/
public function headers() {
return $this->allHeaders;
}
/**
* Get the content of this object.
*
@@ -421,15 +463,27 @@ class RemoteObject extends Object {
throw new \HPCloud\Exception('An unknown exception occurred during transmission.');
}
// Reset the content length, last modified, and etag:
$this->extractFromHeaders($response);
return $response;
}
/**
* Extract information from HTTP headers.
*
* This is used internally to set object properties from headers.
*/
protected function extractFromHeaders($response) {
$this->setContentType($response->header('Content-Type', $this->contentType()));
$this->lastModified = strtotime($response->header('Last-Modified', 0));
$this->etag = $response->header('Etag', $this->etag);
$this->contentLength = (int) $response->header('Content-Length', 0);
$this->setDisposition($response->header('Content-Disposition', NULL));
$this->setEncoding($response->header('Content-Encoding', NULL));
// Reset the metadata, too:
$this->setMetadata(Container::extractHeaderAttributes($response->headers()));
return $response;
}
}

View File

@@ -20,12 +20,22 @@ class RemoteObjectTest extends \HPCloud\Tests\TestCase {
const FCONTENT = 'Rah rah ah ah ah. Roma roma ma. Gaga oh la la.';
const FMETA_NAME = 'Foo';
const FMETA_VALUE = 'Bar';
const FDISPOSITION = 'attachment; roma.gaga';
const FENCODING = 'gzip';
const FCORS_NAME = 'Access-Control-Max-Age';
const FCORS_VALUE = '2000';
protected function createAnObject() {
$container = $this->containerFixture();
$object = new Object(self::FNAME, self::FCONTENT, self::FTYPE);
$object->setMetadata(array(self::FMETA_NAME => self::FMETA_VALUE));
$object->setDisposition(self::FDISPOSITION);
$object->setEncoding(self::FENCODING);
// Need some headers that Swift actually stores and returns. This
// one does not seem to be returned ever.
//$object->setAdditionalHeaders(array(self::FCORS_NAME => self::FCORS_VALUE));
$container->save($object);
}
@@ -94,6 +104,31 @@ class RemoteObjectTest extends \HPCloud\Tests\TestCase {
$this->assertEquals(self::FMETA_VALUE, $md[self::FMETA_NAME]);
}
/**
* @depends testNewFromHeaders
*/
public function testDisposition($obj) {
$this->assertEquals(self::FDISPOSITION, $obj->disposition());
}
/**
* @depends testNewFromHeaders
*/
public function testEncoding($obj) {
$this->assertEquals(self::FENCODING, $obj->encoding());
}
/**
* @depends testNewFromHeaders
*/
public function testHeaders($obj) {
$headers = $obj->headers();
$this->assertTrue(count($headers) > 1);
// Swift doesn't return CORS headers even though it is supposed to.
//$this->assertEquals(self::FCORS_VALUE, $headers[self::FCORS_NAME]);
}
/**
* @depends testNewFromHeaders
*/