From f2774a4d11bb005c0d54928477ad7b82afbf0cf3 Mon Sep 17 00:00:00 2001 From: MORITA Kazutaka Date: Tue, 22 Apr 2014 09:28:15 +0900 Subject: [PATCH] Disable case-changing behavior in Eventlet RFC 2616 says that HTTP header fields are case-insensitive. However, there are some S3 clients who don't accept normalized header by Swift and Eventlet. For example, AWS Java SDK expects that an etag header is 'ETag', not 'Etag'. This patch disables Eventlet's header capitalization so that the swift3 middleware can normalize the response headers as those clients expect. Note that this change requires a fix for Eventlet, which will be included in the next Eventlet release (v0.15). Change-Id: I6d3428b0dafef776bdb3ebac7639b3126fa5e60d --- swift/common/middleware/proxy_logging.py | 4 +- swift/common/wsgi.py | 10 ++++- test/unit/common/test_wsgi.py | 57 ++++++++++++++++++++---- 3 files changed, 59 insertions(+), 12 deletions(-) diff --git a/swift/common/middleware/proxy_logging.py b/swift/common/middleware/proxy_logging.py index b0509fe079..5af43d3cd3 100644 --- a/swift/common/middleware/proxy_logging.py +++ b/swift/common/middleware/proxy_logging.py @@ -253,10 +253,10 @@ class ProxyLoggingMiddleware(object): break else: if not chunk: - start_response_args[0][1].append(('content-length', '0')) + start_response_args[0][1].append(('Content-Length', '0')) elif isinstance(iterable, list): start_response_args[0][1].append( - ('content-length', str(sum(len(i) for i in iterable)))) + ('Content-Length', str(sum(len(i) for i in iterable)))) start_response(*start_response_args[0]) req = Request(env) diff --git a/swift/common/wsgi.py b/swift/common/wsgi.py index 7aab048310..43cee2fc9e 100644 --- a/swift/common/wsgi.py +++ b/swift/common/wsgi.py @@ -16,6 +16,7 @@ """WSGI tools for use with swift.""" import errno +import inspect import os import signal import time @@ -386,7 +387,14 @@ def run_server(conf, logger, sock, global_conf=None): max_clients = int(conf.get('max_clients', '1024')) pool = RestrictedGreenPool(size=max_clients) try: - wsgi.server(sock, app, NullLogger(), custom_pool=pool) + # Disable capitalizing headers in Eventlet if possible. This is + # necessary for the AWS SDK to work with swift3 middleware. + argspec = inspect.getargspec(wsgi.server) + if 'capitalize_response_headers' in argspec.args: + wsgi.server(sock, app, NullLogger(), custom_pool=pool, + capitalize_response_headers=False) + else: + wsgi.server(sock, app, NullLogger(), custom_pool=pool) except socket.error as err: if err[0] != errno.EINVAL: raise diff --git a/test/unit/common/test_wsgi.py b/test/unit/common/test_wsgi.py index 419b94f363..7b2a611a43 100644 --- a/test/unit/common/test_wsgi.py +++ b/test/unit/common/test_wsgi.py @@ -335,10 +335,11 @@ class TestWSGI(unittest.TestCase): 'modify_wsgi_pipeline'): with mock.patch('swift.common.wsgi.wsgi') as _wsgi: with mock.patch('swift.common.wsgi.eventlet') as _eventlet: - conf = wsgi.appconfig(conf_file) - logger = logging.getLogger('test') - sock = listen(('localhost', 0)) - wsgi.run_server(conf, logger, sock) + with mock.patch('swift.common.wsgi.inspect'): + conf = wsgi.appconfig(conf_file) + logger = logging.getLogger('test') + sock = listen(('localhost', 0)) + wsgi.run_server(conf, logger, sock) self.assertEquals('HTTP/1.0', _wsgi.HttpProtocol.default_request_version) self.assertEquals(30, _wsgi.WRITE_TIMEOUT) @@ -356,6 +357,43 @@ class TestWSGI(unittest.TestCase): self.assert_('custom_pool' in kwargs) self.assertEquals(1000, kwargs['custom_pool'].size) + def test_run_server_with_latest_eventlet(self): + config = """ + [DEFAULT] + swift_dir = TEMPDIR + + [pipeline:main] + pipeline = proxy-server + + [app:proxy-server] + use = egg:swift#proxy + """ + + def argspec_stub(server): + return mock.MagicMock(args=['capitalize_response_headers']) + + contents = dedent(config) + with temptree(['proxy-server.conf']) as t: + conf_file = os.path.join(t, 'proxy-server.conf') + with open(conf_file, 'w') as f: + f.write(contents.replace('TEMPDIR', t)) + _fake_rings(t) + with nested( + mock.patch('swift.proxy.server.Application.' + 'modify_wsgi_pipeline'), + mock.patch('swift.common.wsgi.wsgi'), + mock.patch('swift.common.wsgi.eventlet'), + mock.patch('swift.common.wsgi.inspect', + getargspec=argspec_stub)) as (_, _wsgi, _, _): + conf = wsgi.appconfig(conf_file) + logger = logging.getLogger('test') + sock = listen(('localhost', 0)) + wsgi.run_server(conf, logger, sock) + + _wsgi.server.assert_called() + args, kwargs = _wsgi.server.call_args + self.assertEquals(kwargs.get('capitalize_response_headers'), False) + def test_run_server_conf_dir(self): config_dir = { 'proxy-server.conf.d/pipeline.conf': """ @@ -384,11 +422,12 @@ class TestWSGI(unittest.TestCase): with mock.patch('swift.common.wsgi.wsgi') as _wsgi: with mock.patch('swift.common.wsgi.eventlet') as _eventlet: with mock.patch.dict('os.environ', {'TZ': ''}): - conf = wsgi.appconfig(conf_dir) - logger = logging.getLogger('test') - sock = listen(('localhost', 0)) - wsgi.run_server(conf, logger, sock) - self.assert_(os.environ['TZ'] is not '') + with mock.patch('swift.common.wsgi.inspect'): + conf = wsgi.appconfig(conf_dir) + logger = logging.getLogger('test') + sock = listen(('localhost', 0)) + wsgi.run_server(conf, logger, sock) + self.assert_(os.environ['TZ'] is not '') self.assertEquals('HTTP/1.0', _wsgi.HttpProtocol.default_request_version)