commit 0d2604c110828af0fc7fcf423ecfac554669c404 Author: Tim Burke Date: Fri Oct 2 16:41:21 2020 -0700 wsgi: Ensure _response_headers is a list It's reasonably common that apps or middlewares may send back headers via dict.items(), but WSGIContext users often expect the headers to be a list. Convert it for them, to avoid errors like AttributeError: 'dict_items' object has no attribute 'append' Change-Id: I4d061fad4da370c1cbb77ab78a55133319ea2dd7 diff --git a/swift/common/wsgi.py b/swift/common/wsgi.py index 11fcaa4..0fe2837 100644 --- a/swift/common/wsgi.py +++ b/swift/common/wsgi.py @@ -1314,7 +1314,8 @@ class WSGIContext(object): Uses the same semantics as the usual WSGI start_response. """ self._response_status = status - self._response_headers = headers + self._response_headers = \ + headers if isinstance(headers, list) else list(headers) self._response_exc_info = exc_info def _app_call(self, env): diff --git a/test/unit/common/test_wsgi.py b/test/unit/common/test_wsgi.py index cb489dd..0d783f2 100644 --- a/test/unit/common/test_wsgi.py +++ b/test/unit/common/test_wsgi.py @@ -1671,29 +1671,29 @@ class TestWSGIContext(unittest.TestCase): def app(env, start_response): start_response(statuses.pop(0), [('Content-Length', '3')]) - yield 'Ok\n' + yield b'Ok\n' wc = wsgi.WSGIContext(app) r = Request.blank('/') it = wc._app_call(r.environ) self.assertEqual(wc._response_status, '200 Ok') - self.assertEqual(''.join(it), 'Ok\n') + self.assertEqual(b''.join(it), b'Ok\n') r = Request.blank('/') it = wc._app_call(r.environ) self.assertEqual(wc._response_status, '404 Not Found') - self.assertEqual(''.join(it), 'Ok\n') + self.assertEqual(b''.join(it), b'Ok\n') def test_app_iter_is_closable(self): def app(env, start_response): - yield '' - yield '' + yield b'' + yield b'' start_response('200 OK', [('Content-Length', '25')]) - yield 'aaaaa' - yield 'bbbbb' - yield 'ccccc' - yield 'ddddd' - yield 'eeeee' + yield b'aaaaa' + yield b'bbbbb' + yield b'ccccc' + yield b'ddddd' + yield b'eeeee' wc = wsgi.WSGIContext(app) r = Request.blank('/') @@ -1701,8 +1701,8 @@ class TestWSGIContext(unittest.TestCase): self.assertEqual(wc._response_status, '200 OK') iterator = iter(iterable) - self.assertEqual('aaaaa', next(iterator)) - self.assertEqual('bbbbb', next(iterator)) + self.assertEqual(b'aaaaa', next(iterator)) + self.assertEqual(b'bbbbb', next(iterator)) iterable.close() with self.assertRaises(StopIteration): next(iterator) @@ -1712,16 +1712,34 @@ class TestWSGIContext(unittest.TestCase): def app(env, start_response): start_response(statuses.pop(0), [('Content-Length', '30')]) - yield 'Ok\n' + yield b'Ok\n' wc = wsgi.WSGIContext(app) r = Request.blank('/') it = wc._app_call(r.environ) wc.update_content_length(35) self.assertEqual(wc._response_status, '200 Ok') - self.assertEqual(''.join(it), 'Ok\n') + self.assertEqual(b''.join(it), b'Ok\n') self.assertEqual(wc._response_headers, [('Content-Length', '35')]) + def test_app_returns_headers_as_dict_items(self): + statuses = ['200 Ok'] + + def app(env, start_response): + start_response(statuses.pop(0), {'Content-Length': '3'}.items()) + yield b'Ok\n' + + wc = wsgi.WSGIContext(app) + r = Request.blank('/') + it = wc._app_call(r.environ) + wc._response_headers.append(('X-Trans-Id', 'txn')) + self.assertEqual(wc._response_status, '200 Ok') + self.assertEqual(b''.join(it), b'Ok\n') + self.assertEqual(wc._response_headers, [ + ('Content-Length', '3'), + ('X-Trans-Id', 'txn'), + ]) + class TestPipelineWrapper(unittest.TestCase):