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:
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user