objects can now have arbitrary headers set in metadata that will be served back when they are fetched

This commit is contained in:
John Dickinson 2011-03-22 20:05:44 -05:00
parent 68566d38cf
commit 88ad83767b
3 changed files with 34 additions and 9 deletions

View File

@ -30,6 +30,10 @@ use = egg:swift#object
# slow = 1
# on PUTs, sync data every n MB
# 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]
# You can override the default log routing for this app here (don't use set!):

View File

@ -52,6 +52,8 @@ PICKLE_PROTOCOL = 2
METADATA_KEY = 'user.swift.metadata'
MAX_OBJECT_NAME_LENGTH = 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):
@ -137,8 +139,7 @@ class DiskFile(object):
if self.meta_file:
with open(self.meta_file) as mfp:
for key in self.metadata.keys():
if key.lower() not in ('content-type',
'deleted', 'content-length', 'etag'):
if key.lower() not in DISALLOWED_HEADERS:
del self.metadata[key]
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.slow = int(conf.get('slow', 0))
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,
headers_out, objdevice):
@ -353,9 +358,11 @@ class ObjectController(object):
metadata = {'X-Timestamp': request.headers['x-timestamp']}
metadata.update(val for val in request.headers.iteritems()
if val[0].lower().startswith('x-object-meta-'))
if 'content-encoding' in request.headers:
metadata['Content-Encoding'] = \
request.headers['Content-Encoding']
for header_key in self.allowed_headers:
if header_key in request.headers:
header_caps = \
header_key.replace('-', ' ').title().replace(' ', '-')
metadata[header_caps] = request.headers[header_key]
with file.mkstemp() as (fd, tmppath):
file.put(fd, tmppath, metadata, extension='.meta')
return response_class(request=request)
@ -420,9 +427,11 @@ class ObjectController(object):
metadata.update(val for val in request.headers.iteritems()
if val[0].lower().startswith('x-object-meta-') and
len(val[0]) > 14)
if 'content-encoding' in request.headers:
metadata['Content-Encoding'] = \
request.headers['Content-Encoding']
for header_key in self.allowed_headers:
if header_key in request.headers:
header_caps = \
header_key.replace('-', ' ').title().replace(' ', '-')
metadata[header_caps] = request.headers[header_key]
file.put(fd, tmppath, metadata)
file.unlinkold(metadata['X-Timestamp'])
self.container_update('PUT', account, container, obj, request.headers,
@ -487,7 +496,8 @@ class ObjectController(object):
request=request, conditional_response=True)
for key, value in file.metadata.iteritems():
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.etag = file.metadata['ETag']
response.last_modified = float(file.metadata['X-Timestamp'])

View File

@ -57,10 +57,14 @@ class TestObjectController(unittest.TestCase):
def test_POST_update_meta(self):
""" 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())
req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
headers={'X-Timestamp': timestamp,
'Content-Type': 'application/x-test',
'Foo': 'fooheader',
'Baz': 'bazheader',
'X-Object-Meta-1': 'One',
'X-Object-Meta-Two': 'Two'})
req.body = 'VERIFY'
@ -74,6 +78,8 @@ class TestObjectController(unittest.TestCase):
'X-Object-Meta-3': 'Three',
'X-Object-Meta-4': 'Four',
'Content-Encoding': 'gzip',
'Foo': 'fooheader',
'Bar': 'barheader',
'Content-Type': 'application/x-test'})
resp = self.object_controller.POST(req)
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-3" 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)
self.assertEquals(resp.headers['Content-Type'], 'application/x-test')
@ -98,6 +107,8 @@ class TestObjectController(unittest.TestCase):
resp = self.object_controller.GET(req)
self.assert_("X-Object-Meta-3" 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)
self.assertEquals(resp.headers['Content-Type'], 'application/x-test')