Added swift3.Response

Swift normalizes response headers with Python's str.title().  However, there
are some S3 clients who don't accept such kind of headers.  For example, AWS
Java SDK expects that a etag header is 'ETag', not 'Etag'.

This patch introduce our own Response class, which inherits swob.Response.  The
main differences from the original class are:

 - Normalizes response headers as S3 clients expect.
 - Replaces Swift specific headers like 'x-object-meta' with S3's ones.

Change-Id: I4a4443fc0b75e75ea7e2e45eede43f12b040a7a3
This commit is contained in:
MORITA Kazutaka
2014-05-27 10:22:04 +09:00
parent bb8d99d1cf
commit 4318780e8b
2 changed files with 132 additions and 16 deletions

View File

@@ -63,7 +63,8 @@ import datetime
import re
from swift.common.utils import get_logger
from swift.common.swob import Request, Response, HTTPForbidden, HTTPConflict, \
from swift.common.swob import Request
from swift3.response import Response, HTTPForbidden, HTTPConflict, \
HTTPBadRequest, HTTPMethodNotAllowed, HTTPNotFound, HTTPNotImplemented, \
HTTPLengthRequired, HTTPServiceUnavailable, HTTPNoContent, HTTPOk
from swift.common.http import HTTP_OK, HTTP_CREATED, HTTP_ACCEPTED, \
@@ -439,11 +440,6 @@ class BucketController(Controller):
if status == HTTP_NO_CONTENT:
status = HTTP_OK
if 'x-container-object-count' in headers:
headers['x-rgw-object-count'] = headers['x-container-object-count']
if 'x-container-bytes-used' in headers:
headers['x-rgw-bytes-used'] = headers['x-container-bytes-used']
return Response(status=status, headers=headers, app_iter=resp.app_iter)
def GET(self, req):
@@ -591,16 +587,7 @@ class ObjectController(Controller):
resp.app_iter = None
if is_success(status):
new_hdrs = {}
for key, val in headers.iteritems():
_key = key.lower()
if _key.startswith('x-object-meta-'):
new_hdrs['x-amz-meta-' + key[14:]] = val
elif _key in ('content-length', 'content-type',
'content-range', 'content-encoding',
'etag', 'last-modified'):
new_hdrs[key] = val
return Response(status=status, headers=new_hdrs,
return Response(status=status, headers=headers,
app_iter=resp.app_iter)
elif status in (HTTP_UNAUTHORIZED, HTTP_FORBIDDEN):
return get_err_response('AccessDenied')

129
swift3/response.py Normal file
View File

@@ -0,0 +1,129 @@
# Copyright (c) 2014 OpenStack Foundation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from functools import partial
from swift.common import swob
class HeaderKey(str):
"""
A string object that normalizes string as S3 clients expect with title().
"""
def title(self):
if self.lower() == 'etag':
# AWS Java SDK expects only 'ETag'.
return 'ETag'
if self.lower().startswith('x-amz-'):
# AWS headers returned by S3 are lowercase.
return self.lower()
if self.lower().startswith('x-rgw-'):
# ceph/s3tests expects the header is lowercase.
return self.lower()
return str.title(self)
class HeaderKeyDict(swob.HeaderKeyDict):
"""
Similar to the HeaderKeyDict class in Swift, but its key name is normalized
as S3 clients expect.
"""
def __getitem__(self, key):
return swob.HeaderKeyDict.__getitem__(self, HeaderKey(key))
def __setitem__(self, key, value):
return swob.HeaderKeyDict.__setitem__(self, HeaderKey(key), value)
def __contains__(self, key):
return swob.HeaderKeyDict.__contains__(self, HeaderKey(key))
def __delitem__(self, key):
return swob.HeaderKeyDict.__delitem__(self, HeaderKey(key))
def get(self, key, default=None):
return swob.HeaderKeyDict.get(self, HeaderKey(key), default)
def pop(self, key, default=None):
return swob.HeaderKeyDict.pop(self, HeaderKey(key), default)
class Response(swob.Response):
"""
Similar to the Response class in Swift, but uses our HeaderKeyDict for
headers instead of Swift's HeaderKeyDict. This also translates Swift
specific headers to S3 headers.
"""
def __init__(self, *args, **kwargs):
swob.Response.__init__(self, *args, **kwargs)
headers = HeaderKeyDict()
for key, val in self.headers.iteritems():
_key = key.lower()
if _key.startswith('x-object-meta-'):
headers['x-amz-meta-' + key[14:]] = val
elif _key in ('content-length', 'content-type',
'content-range', 'content-encoding',
'etag', 'last-modified'):
headers[key] = val
elif _key == 'x-container-object-count':
# for ceph/s3tests
headers['x-rgw-object-count'] = val
elif _key == 'x-container-bytes-used':
# for ceph/s3tests
headers['x-rgw-bytes-used'] = val
self.headers = headers
class StatusMap(object):
"""
Similar to the StatusMap class in Swift, but returns swift3.Response
subclasses instead of swob.HTTPException.
"""
def __getitem__(self, key):
return partial(Response, status=key)
status_map = StatusMap()
HTTPOk = status_map[200]
HTTPCreated = status_map[201]
HTTPAccepted = status_map[202]
HTTPNoContent = status_map[204]
HTTPMovedPermanently = status_map[301]
HTTPFound = status_map[302]
HTTPSeeOther = status_map[303]
HTTPNotModified = status_map[304]
HTTPTemporaryRedirect = status_map[307]
HTTPBadRequest = status_map[400]
HTTPUnauthorized = status_map[401]
HTTPForbidden = status_map[403]
HTTPMethodNotAllowed = status_map[405]
HTTPNotFound = status_map[404]
HTTPNotAcceptable = status_map[406]
HTTPRequestTimeout = status_map[408]
HTTPConflict = status_map[409]
HTTPLengthRequired = status_map[411]
HTTPPreconditionFailed = status_map[412]
HTTPRequestEntityTooLarge = status_map[413]
HTTPRequestedRangeNotSatisfiable = status_map[416]
HTTPUnprocessableEntity = status_map[422]
HTTPClientDisconnect = status_map[499]
HTTPServerError = status_map[500]
HTTPInternalServerError = status_map[500]
HTTPNotImplemented = status_map[501]
HTTPBadGateway = status_map[502]
HTTPServiceUnavailable = status_map[503]
HTTPInsufficientStorage = status_map[507]