objects can now have arbitrary headers set in metadata that will be served back when they are fetched
This commit is contained in:
		| @@ -30,6 +30,10 @@ use = egg:swift#object | |||||||
| # slow = 1 | # slow = 1 | ||||||
| # on PUTs, sync data every n MB | # on PUTs, sync data every n MB | ||||||
| # mb_per_sync = 512 | # mb_per_sync = 512 | ||||||
|  | # Comma separated list of headers that can be set in metadata on an object. | ||||||
|  | # This list is in addition to X-Object-Meta-* headers and cannot include | ||||||
|  | # Content-Type, etag, Content-Length, or deleted | ||||||
|  | # allowed_headers = Content-Encoding | ||||||
|  |  | ||||||
| [object-replicator] | [object-replicator] | ||||||
| # You can override the default log routing for this app here (don't use set!): | # You can override the default log routing for this app here (don't use set!): | ||||||
|   | |||||||
| @@ -52,6 +52,8 @@ PICKLE_PROTOCOL = 2 | |||||||
| METADATA_KEY = 'user.swift.metadata' | METADATA_KEY = 'user.swift.metadata' | ||||||
| MAX_OBJECT_NAME_LENGTH = 1024 | MAX_OBJECT_NAME_LENGTH = 1024 | ||||||
| KEEP_CACHE_SIZE = (5 * 1024 * 1024) | KEEP_CACHE_SIZE = (5 * 1024 * 1024) | ||||||
|  | # keep these lower-case | ||||||
|  | DISALLOWED_HEADERS = set('content-length content-type deleted etag'.split()) | ||||||
|  |  | ||||||
|  |  | ||||||
| def read_metadata(fd): | def read_metadata(fd): | ||||||
| @@ -137,8 +139,7 @@ class DiskFile(object): | |||||||
|         if self.meta_file: |         if self.meta_file: | ||||||
|             with open(self.meta_file) as mfp: |             with open(self.meta_file) as mfp: | ||||||
|                 for key in self.metadata.keys(): |                 for key in self.metadata.keys(): | ||||||
|                     if key.lower() not in ('content-type', |                     if key.lower() not in DISALLOWED_HEADERS: | ||||||
|                                 'deleted', 'content-length', 'etag'): |  | ||||||
|                         del self.metadata[key] |                         del self.metadata[key] | ||||||
|                 self.metadata.update(read_metadata(mfp)) |                 self.metadata.update(read_metadata(mfp)) | ||||||
|  |  | ||||||
| @@ -278,6 +279,10 @@ class ObjectController(object): | |||||||
|         self.max_upload_time = int(conf.get('max_upload_time', 86400)) |         self.max_upload_time = int(conf.get('max_upload_time', 86400)) | ||||||
|         self.slow = int(conf.get('slow', 0)) |         self.slow = int(conf.get('slow', 0)) | ||||||
|         self.bytes_per_sync = int(conf.get('mb_per_sync', 512)) * 1024 * 1024 |         self.bytes_per_sync = int(conf.get('mb_per_sync', 512)) * 1024 * 1024 | ||||||
|  |         default_allowed_headers = 'content-encoding' | ||||||
|  |         self.allowed_headers = set(i.strip().lower() for i in \ | ||||||
|  |                 conf.get('allowed_headers', \ | ||||||
|  |                 default_allowed_headers).split(',') if i.strip()) | ||||||
|  |  | ||||||
|     def container_update(self, op, account, container, obj, headers_in, |     def container_update(self, op, account, container, obj, headers_in, | ||||||
|                          headers_out, objdevice): |                          headers_out, objdevice): | ||||||
| @@ -353,9 +358,11 @@ class ObjectController(object): | |||||||
|         metadata = {'X-Timestamp': request.headers['x-timestamp']} |         metadata = {'X-Timestamp': request.headers['x-timestamp']} | ||||||
|         metadata.update(val for val in request.headers.iteritems() |         metadata.update(val for val in request.headers.iteritems() | ||||||
|                 if val[0].lower().startswith('x-object-meta-')) |                 if val[0].lower().startswith('x-object-meta-')) | ||||||
|         if 'content-encoding' in request.headers: |         for header_key in self.allowed_headers: | ||||||
|             metadata['Content-Encoding'] = \ |             if header_key in request.headers: | ||||||
|                 request.headers['Content-Encoding'] |                 header_caps = \ | ||||||
|  |                     header_key.replace('-', ' ').title().replace(' ', '-') | ||||||
|  |                 metadata[header_caps] = request.headers[header_key] | ||||||
|         with file.mkstemp() as (fd, tmppath): |         with file.mkstemp() as (fd, tmppath): | ||||||
|             file.put(fd, tmppath, metadata, extension='.meta') |             file.put(fd, tmppath, metadata, extension='.meta') | ||||||
|         return response_class(request=request) |         return response_class(request=request) | ||||||
| @@ -420,9 +427,11 @@ class ObjectController(object): | |||||||
|             metadata.update(val for val in request.headers.iteritems() |             metadata.update(val for val in request.headers.iteritems() | ||||||
|                     if val[0].lower().startswith('x-object-meta-') and |                     if val[0].lower().startswith('x-object-meta-') and | ||||||
|                     len(val[0]) > 14) |                     len(val[0]) > 14) | ||||||
|             if 'content-encoding' in request.headers: |             for header_key in self.allowed_headers: | ||||||
|                 metadata['Content-Encoding'] = \ |                 if header_key in request.headers: | ||||||
|                     request.headers['Content-Encoding'] |                     header_caps = \ | ||||||
|  |                         header_key.replace('-', ' ').title().replace(' ', '-') | ||||||
|  |                     metadata[header_caps] = request.headers[header_key] | ||||||
|             file.put(fd, tmppath, metadata) |             file.put(fd, tmppath, metadata) | ||||||
|         file.unlinkold(metadata['X-Timestamp']) |         file.unlinkold(metadata['X-Timestamp']) | ||||||
|         self.container_update('PUT', account, container, obj, request.headers, |         self.container_update('PUT', account, container, obj, request.headers, | ||||||
| @@ -487,7 +496,8 @@ class ObjectController(object): | |||||||
|                         request=request, conditional_response=True) |                         request=request, conditional_response=True) | ||||||
|         for key, value in file.metadata.iteritems(): |         for key, value in file.metadata.iteritems(): | ||||||
|             if key == 'X-Object-Manifest' or \ |             if key == 'X-Object-Manifest' or \ | ||||||
|                     key.lower().startswith('x-object-meta-'): |                     key.lower().startswith('x-object-meta-') or \ | ||||||
|  |                     key.lower() in self.allowed_headers: | ||||||
|                 response.headers[key] = value |                 response.headers[key] = value | ||||||
|         response.etag = file.metadata['ETag'] |         response.etag = file.metadata['ETag'] | ||||||
|         response.last_modified = float(file.metadata['X-Timestamp']) |         response.last_modified = float(file.metadata['X-Timestamp']) | ||||||
|   | |||||||
| @@ -57,10 +57,14 @@ class TestObjectController(unittest.TestCase): | |||||||
|  |  | ||||||
|     def test_POST_update_meta(self): |     def test_POST_update_meta(self): | ||||||
|         """ Test swift.object_server.ObjectController.POST """ |         """ Test swift.object_server.ObjectController.POST """ | ||||||
|  |         test_headers = 'content-encoding foo bar'.split() | ||||||
|  |         self.object_controller.allowed_headers = set(test_headers) | ||||||
|         timestamp = normalize_timestamp(time()) |         timestamp = normalize_timestamp(time()) | ||||||
|         req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, |         req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, | ||||||
|                             headers={'X-Timestamp': timestamp, |                             headers={'X-Timestamp': timestamp, | ||||||
|                                      'Content-Type': 'application/x-test', |                                      'Content-Type': 'application/x-test', | ||||||
|  |                                      'Foo': 'fooheader', | ||||||
|  |                                      'Baz': 'bazheader', | ||||||
|                                      'X-Object-Meta-1': 'One', |                                      'X-Object-Meta-1': 'One', | ||||||
|                                      'X-Object-Meta-Two': 'Two'}) |                                      'X-Object-Meta-Two': 'Two'}) | ||||||
|         req.body = 'VERIFY' |         req.body = 'VERIFY' | ||||||
| @@ -74,6 +78,8 @@ class TestObjectController(unittest.TestCase): | |||||||
|                                      'X-Object-Meta-3': 'Three', |                                      'X-Object-Meta-3': 'Three', | ||||||
|                                      'X-Object-Meta-4': 'Four', |                                      'X-Object-Meta-4': 'Four', | ||||||
|                                      'Content-Encoding': 'gzip', |                                      'Content-Encoding': 'gzip', | ||||||
|  |                                      'Foo': 'fooheader', | ||||||
|  |                                      'Bar': 'barheader', | ||||||
|                                      'Content-Type': 'application/x-test'}) |                                      'Content-Type': 'application/x-test'}) | ||||||
|         resp = self.object_controller.POST(req) |         resp = self.object_controller.POST(req) | ||||||
|         self.assertEquals(resp.status_int, 202) |         self.assertEquals(resp.status_int, 202) | ||||||
| @@ -84,6 +90,9 @@ class TestObjectController(unittest.TestCase): | |||||||
|                      "X-Object-Meta-Two" not in resp.headers and \ |                      "X-Object-Meta-Two" not in resp.headers and \ | ||||||
|                      "X-Object-Meta-3" in resp.headers and \ |                      "X-Object-Meta-3" in resp.headers and \ | ||||||
|                      "X-Object-Meta-4" in resp.headers and \ |                      "X-Object-Meta-4" in resp.headers and \ | ||||||
|  |                      "Foo" in resp.headers and \ | ||||||
|  |                      "Bar" in resp.headers and \ | ||||||
|  |                      "Baz" not in resp.headers and \ | ||||||
|                      "Content-Encoding" in resp.headers) |                      "Content-Encoding" in resp.headers) | ||||||
|         self.assertEquals(resp.headers['Content-Type'], 'application/x-test') |         self.assertEquals(resp.headers['Content-Type'], 'application/x-test') | ||||||
|  |  | ||||||
| @@ -98,6 +107,8 @@ class TestObjectController(unittest.TestCase): | |||||||
|         resp = self.object_controller.GET(req) |         resp = self.object_controller.GET(req) | ||||||
|         self.assert_("X-Object-Meta-3" not in resp.headers and \ |         self.assert_("X-Object-Meta-3" not in resp.headers and \ | ||||||
|                      "X-Object-Meta-4" not in resp.headers and \ |                      "X-Object-Meta-4" not in resp.headers and \ | ||||||
|  |                      "Foo" not in resp.headers and \ | ||||||
|  |                      "Bar" not in resp.headers and \ | ||||||
|                      "Content-Encoding" not in resp.headers) |                      "Content-Encoding" not in resp.headers) | ||||||
|         self.assertEquals(resp.headers['Content-Type'], 'application/x-test') |         self.assertEquals(resp.headers['Content-Type'], 'application/x-test') | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 John Dickinson
					John Dickinson