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();
|
$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.
|
// Auth token.
|
||||||
$headers['X-Auth-Token'] = $this->token;
|
$headers['X-Auth-Token'] = $this->token;
|
||||||
|
|
||||||
|
// Add any custom headers:
|
||||||
|
$moreHeaders = $obj->additionalHeaders();
|
||||||
|
if (!empty($moreHeaders)) {
|
||||||
|
$headers += $moreHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
$client = \HPCloud\Transport::instance();
|
$client = \HPCloud\Transport::instance();
|
||||||
|
|
||||||
$response = $client->doRequest($url, 'PUT', $headers, $obj->content());
|
$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
|
* else. This is a convenient way to set additional metadata without
|
||||||
* having to re-upload a potentially large object.
|
* 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
|
* @param \HPCloud\Storage\ObjectStorage\Object $obj
|
||||||
* The object to update.
|
* The object to update.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -63,6 +63,14 @@ class Object {
|
|||||||
*/
|
*/
|
||||||
protected $metadata = array();
|
protected $metadata = array();
|
||||||
|
|
||||||
|
protected $contentEncoding;
|
||||||
|
protected $contentDisposition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension mechanism for new headers.
|
||||||
|
*/
|
||||||
|
protected $additionalHeaders = array();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new object for storage.
|
* Construct a new object for storage.
|
||||||
@@ -302,6 +310,126 @@ class Object {
|
|||||||
return md5($this->content);
|
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.
|
* This object should be transmitted in chunks.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -35,6 +35,17 @@ class RemoteObject extends Object {
|
|||||||
protected $contentVerification = TRUE;
|
protected $contentVerification = TRUE;
|
||||||
protected $caching = FALSE;
|
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.
|
* Create a new RemoteObject from JSON data.
|
||||||
*
|
*
|
||||||
@@ -57,6 +68,9 @@ class RemoteObject extends Object {
|
|||||||
$object->token = $token;
|
$object->token = $token;
|
||||||
$object->url = $url;
|
$object->url = $url;
|
||||||
|
|
||||||
|
// FIXME: What do we do about HTTP header data that doesn't come
|
||||||
|
// back in JSON?
|
||||||
|
|
||||||
return $object;
|
return $object;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +94,8 @@ class RemoteObject extends Object {
|
|||||||
public static function newFromHeaders($name, $headers, $token, $url) {
|
public static function newFromHeaders($name, $headers, $token, $url) {
|
||||||
$object = new RemoteObject($name);
|
$object = new RemoteObject($name);
|
||||||
|
|
||||||
|
$object->allHeaders = $headers;
|
||||||
|
|
||||||
//throw new \Exception(print_r($headers, TRUE));
|
//throw new \Exception(print_r($headers, TRUE));
|
||||||
|
|
||||||
$object->setContentType($headers['Content-Type']);
|
$object->setContentType($headers['Content-Type']);
|
||||||
@@ -90,6 +106,17 @@ class RemoteObject extends Object {
|
|||||||
// Set the metadata, too.
|
// Set the metadata, too.
|
||||||
$object->setMetadata(Container::extractHeaderAttributes($headers));
|
$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->token = $token;
|
||||||
$object->url = $url;
|
$object->url = $url;
|
||||||
|
|
||||||
@@ -138,6 +165,21 @@ class RemoteObject extends Object {
|
|||||||
return $this->metadata;
|
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.
|
* Get the content of this object.
|
||||||
*
|
*
|
||||||
@@ -421,15 +463,27 @@ class RemoteObject extends Object {
|
|||||||
throw new \HPCloud\Exception('An unknown exception occurred during transmission.');
|
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->setContentType($response->header('Content-Type', $this->contentType()));
|
||||||
$this->lastModified = strtotime($response->header('Last-Modified', 0));
|
$this->lastModified = strtotime($response->header('Last-Modified', 0));
|
||||||
$this->etag = $response->header('Etag', $this->etag);
|
$this->etag = $response->header('Etag', $this->etag);
|
||||||
$this->contentLength = (int) $response->header('Content-Length', 0);
|
$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:
|
// Reset the metadata, too:
|
||||||
$this->setMetadata(Container::extractHeaderAttributes($response->headers()));
|
$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 FCONTENT = 'Rah rah ah ah ah. Roma roma ma. Gaga oh la la.';
|
||||||
const FMETA_NAME = 'Foo';
|
const FMETA_NAME = 'Foo';
|
||||||
const FMETA_VALUE = 'Bar';
|
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() {
|
protected function createAnObject() {
|
||||||
$container = $this->containerFixture();
|
$container = $this->containerFixture();
|
||||||
|
|
||||||
$object = new Object(self::FNAME, self::FCONTENT, self::FTYPE);
|
$object = new Object(self::FNAME, self::FCONTENT, self::FTYPE);
|
||||||
$object->setMetadata(array(self::FMETA_NAME => self::FMETA_VALUE));
|
$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);
|
$container->save($object);
|
||||||
}
|
}
|
||||||
@@ -94,6 +104,31 @@ class RemoteObjectTest extends \HPCloud\Tests\TestCase {
|
|||||||
$this->assertEquals(self::FMETA_VALUE, $md[self::FMETA_NAME]);
|
$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
|
* @depends testNewFromHeaders
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user