| #!/usr/bin/env python |
| # |
| # Copyright 2007 Google Inc. |
| # |
| # 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. |
| # |
| """Tests for devappserver2.request_rewriter.""" |
| |
| |
| import functools |
| import sys |
| import time |
| import unittest |
| |
| import google |
| import mox |
| |
| from google.appengine.tools.devappserver2 import request_rewriter |
| from google.appengine.tools.devappserver2 import wsgi_test_utils |
| |
| |
| class RewriterMiddlewareTest(wsgi_test_utils.RewriterTestCase): |
| """Tests the response rewriter middleware.""" |
| |
| def setUp(self): |
| """Replace the rewriter chain with a series of dummy rewriters.""" |
| # Used for remembering calls to rewriters. |
| self.initial_calls = 0 |
| self.chain_calls = 0 |
| self.modify_calls = 0 |
| # Individual cases can set these to False to prevent the rewriters from |
| # checking the expected status and/or body. |
| self.test_status = True |
| self.test_body = True |
| |
| def check_initial_response(state): |
| self.assertEquals('Environ value', state.environ['ENVIRON_KEY']) |
| if self.test_status: |
| self.assertEquals('200 Good to go', state.status) |
| self.assertEquals('Some value', state.headers['SomeHeader']) |
| |
| # Convert state.body into a list to allow multiple traversals. |
| state.body = list(state.body) |
| if self.test_body: |
| self.assertEquals('Original content', ''.join(state.body)) |
| |
| self.initial_calls += 1 |
| |
| def check_chain(unused_state): |
| self.chain_calls += 1 |
| |
| def modify_status(state): |
| if self.test_status: |
| self.assertEquals('200 Good to go', state.status) |
| if not state.status.startswith('500 '): |
| state.status = '400 Not so good' |
| self.modify_calls += 1 |
| |
| def modify_headers(state): |
| if self.test_status: |
| self.assertEquals('400 Not so good', state.status) |
| self.assertEquals('Some value', state.headers['SomeHeader']) |
| state.headers.add_header('AnotherHeader', 'Another value') |
| self.modify_calls += 1 |
| |
| def modify_body(state): |
| body = ''.join(state.body) |
| self.assertEquals('Another value', state.headers['AnotherHeader']) |
| self.assertEquals('Some value', state.headers['SomeHeader']) |
| if self.test_body: |
| self.assertEquals('Original content', body) |
| |
| state.body = ('%s%say ' % (w[1:], w[:1].lower()) for w in body.split()) |
| self.modify_calls += 1 |
| |
| response_rewriter_chain = [check_initial_response, |
| check_chain, |
| check_chain, |
| modify_status, |
| modify_headers, |
| modify_body, |
| ] |
| |
| def test_middleware(application): |
| return functools.partial(request_rewriter._rewriter_middleware, [], |
| response_rewriter_chain, application) |
| self.rewriter_middleware = test_middleware |
| |
| def test_rewrite_response_chain(self): |
| """Tests that rewriter_middleware correctly chains rewriters.""" |
| environ = {'ENVIRON_KEY': 'Environ value'} |
| application = wsgi_test_utils.constant_app( |
| '200 Good to go', |
| [('SomeHeader', 'Some value')], |
| 'Original content') |
| |
| expected_status = '400 Not so good' |
| |
| expected_headers = { |
| 'AnotherHeader': 'Another value', |
| 'SomeHeader': 'Some value', |
| } |
| |
| expected_body = 'riginaloay ontentcay ' |
| |
| self.assert_rewritten_response(expected_status, expected_headers, |
| expected_body, application, environ) |
| |
| self.assertEquals(1, self.initial_calls) |
| self.assertEquals(2, self.chain_calls) |
| self.assertEquals(3, self.modify_calls) |
| |
| def test_body_no_yields(self): |
| """Tests an application that yields 0 body blocks.""" |
| |
| def application(unused_environ, start_response): |
| start_response('200 Good to go', [('SomeHeader', 'Some value')]) |
| return [] |
| |
| environ = {'ENVIRON_KEY': 'Environ value'} |
| |
| expected_status = '400 Not so good' |
| |
| expected_headers = { |
| 'AnotherHeader': 'Another value', |
| 'SomeHeader': 'Some value', |
| } |
| |
| expected_body = '' |
| |
| self.test_body = False |
| self.assert_rewritten_response(expected_status, expected_headers, |
| expected_body, application, environ) |
| |
| def test_body_multiple_yields(self): |
| """Tests an application that yields several body blocks.""" |
| |
| def application(unused_environ, start_response): |
| start_response('200 Good to go', [('SomeHeader', 'Some value')]) |
| yield 'Origin' |
| yield 'al content' |
| |
| environ = {'ENVIRON_KEY': 'Environ value'} |
| |
| expected_status = '400 Not so good' |
| |
| expected_headers = { |
| 'AnotherHeader': 'Another value', |
| 'SomeHeader': 'Some value', |
| } |
| |
| expected_body = 'riginaloay ontentcay ' |
| |
| self.assert_rewritten_response(expected_status, expected_headers, |
| expected_body, application, environ) |
| |
| def test_body_write_and_iterable(self): |
| """Tests an application that calls write() and returns an iterable.""" |
| |
| def application(unused_environ, start_response): |
| write = start_response('200 Good to go', [('SomeHeader', 'Some value')]) |
| write('Origin') |
| return ['al content'] |
| |
| environ = {'ENVIRON_KEY': 'Environ value'} |
| |
| expected_status = '400 Not so good' |
| |
| expected_headers = { |
| 'AnotherHeader': 'Another value', |
| 'SomeHeader': 'Some value', |
| } |
| |
| expected_body = 'riginaloay ontentcay ' |
| |
| self.assert_rewritten_response(expected_status, expected_headers, |
| expected_body, application, environ) |
| |
| def test_body_exception_before_yield(self): |
| """Tests an application that raises an exception before yielding.""" |
| |
| def application(unused_environ, start_response): |
| start_response('200 Good to go', [('SomeHeader', 'Some value')]) |
| # Yield some empty blocks -- these should not prevent us from changing the |
| # status and headers. |
| yield '' |
| yield '' |
| try: |
| raise ValueError('A problem happened') |
| except ValueError, e: |
| exc_info = sys.exc_info() |
| start_response('500 Internal Server Error', |
| [('SomeHeader', 'Some value')], exc_info) |
| yield str(e) |
| else: |
| yield 'Origin' |
| yield 'al content' |
| |
| environ = {'ENVIRON_KEY': 'Environ value'} |
| |
| expected_status = '500 Internal Server Error' |
| |
| expected_headers = { |
| 'AnotherHeader': 'Another value', |
| 'SomeHeader': 'Some value', |
| } |
| |
| expected_body = 'aay roblempay appenedhay ' |
| |
| self.test_status = False |
| self.test_body = False |
| self.assert_rewritten_response(expected_status, expected_headers, |
| expected_body, application, environ) |
| |
| def test_body_exception_after_yield(self): |
| """Tests an application that raises an exception before yielding.""" |
| |
| def application(unused_environ, start_response): |
| start_response('200 Good to go', [('SomeHeader', 'Some value')]) |
| yield 'Origin' |
| try: |
| raise ValueError('A problem happened') |
| except ValueError, e: |
| exc_info = sys.exc_info() |
| start_response('500 Internal Server Error', |
| [('SomeHeader', 'Some value')], exc_info) |
| yield str(e) |
| else: |
| yield 'al content' |
| |
| environ = {'ENVIRON_KEY': 'Environ value'} |
| |
| self.assertRaisesRegexp(ValueError, 'A problem happened$', |
| self.assert_rewritten_response, None, None, None, |
| application, environ) |
| |
| |
| class RequestRewritersTest(wsgi_test_utils.RewriterTestCase): |
| """Tests the request rewriter.""" |
| |
| def test_request_header_sanitation(self): |
| """Tests that the invalid request headers are stripped out.""" |
| |
| input_environ = { |
| 'HTTP_ACCEPT_ENCODING': 'gzip', |
| 'HTTP_CONNECTION': 'close', |
| 'HTTP_CONTENT_TYPE': 'text/html', |
| 'HTTP_KEEP_ALIVE': 'foo', |
| 'HTTP_PROXY_AUTHORIZATION': 'me', |
| 'HTTP_TE': 'deflate', |
| 'HTTP_TRAILER': 'X-Bar', |
| 'HTTP_TRANSFER_ENCODING': 'chunked', |
| 'HTTP_X_FOO': 'bar', |
| } |
| |
| expected_environ = { |
| 'HTTP_CONTENT_TYPE': 'text/html', |
| 'HTTP_X_FOO': 'bar', |
| } |
| |
| def application(environ, start_response): |
| self.assertDictEqual(expected_environ, environ) |
| |
| start_response('200 OK', []) |
| return ['ok'] |
| |
| def start_response(unused_status, unused_headers, unused_exc_info=None): |
| pass |
| |
| wrapped_application = request_rewriter.frontend_rewriter_middleware( |
| application) |
| |
| wrapped_application(input_environ, start_response) |
| |
| |
| class ResponseRewritersTest(wsgi_test_utils.RewriterTestCase): |
| """Tests the actual response rewriter chain.""" |
| |
| def test_content_length_rewrite(self): |
| """Tests when the 'Content-Length' header needs to be updated.""" |
| application = wsgi_test_utils.constant_app( |
| '200 OK', |
| [('Content-Type', 'text/html'), |
| ('Cache-Control', 'no-cache'), |
| ('Expires', 'Fri, 01 Jan 1990 00:00:00 GMT'), |
| ('Content-Length', '1234'), |
| ], |
| 'this is my data') |
| |
| expected_status = '200 OK' |
| |
| expected_headers = { |
| 'Content-Type': 'text/html', |
| 'Cache-Control': 'no-cache', |
| 'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT', |
| 'Content-Length': '15', |
| } |
| |
| expected_body = 'this is my data' |
| |
| self.assert_rewritten_response(expected_status, expected_headers, |
| expected_body, application) |
| |
| def test_too_big_rewrite(self): |
| """Tests that a response that is too big is rejected.""" |
| application = wsgi_test_utils.constant_app( |
| '200 OK', |
| [('Content-Type', 'text/plain'), |
| ('Cache-Control', 'no-cache'), |
| ('Expires', 'Fri, 01 Jan 1990 00:00:00 GMT'), |
| ('Content-Length', '1234'), |
| ], |
| 'x' * 33554433) # 32 MB + 1 byte |
| |
| expected_status = '500 Internal Server Error' |
| |
| expected_headers = { |
| 'Content-Type': 'text/html', |
| 'Cache-Control': 'no-cache', |
| 'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT', |
| 'Content-Length': '63', |
| } |
| |
| expected_body = ('HTTP response was too large: 33554433. The limit is: ' |
| '33554432.\n') |
| |
| self.assert_rewritten_response(expected_status, expected_headers, |
| expected_body, application) |
| |
| def test_head_method_preserves_content_length(self): |
| """Tests that a HEAD request does not delete or alter the Content-Length.""" |
| environ = {'REQUEST_METHOD': 'HEAD'} |
| |
| application = wsgi_test_utils.constant_app( |
| '200 OK', |
| [('Content-Type', 'text/html'), |
| ('Cache-Control', 'no-cache'), |
| ('Expires', 'Fri, 01 Jan 1990 00:00:00 GMT'), |
| ('Content-Length', '1234'), |
| ], |
| 'this is my data') |
| |
| expected_status = '200 OK' |
| |
| expected_headers = { |
| 'Content-Type': 'text/html', |
| 'Cache-Control': 'no-cache', |
| 'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT', |
| 'Content-Length': '1234', |
| } |
| |
| # Also expect that the body is deleted. |
| expected_body = '' |
| |
| self.assert_rewritten_response(expected_status, expected_headers, |
| expected_body, application, environ) |
| |
| def test_default_content_type(self): |
| """Tests that the default Content-Type header is applied.""" |
| application = wsgi_test_utils.constant_app( |
| '200 OK', |
| [('Cache-Control', 'no-cache'), |
| ('Expires', 'Fri, 01 Jan 1990 00:00:00 GMT'), |
| ], |
| 'this is my data') |
| |
| expected_status = '200 OK' |
| |
| expected_headers = { |
| 'Content-Type': 'text/html', |
| 'Cache-Control': 'no-cache', |
| 'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT', |
| 'Content-Length': '15', |
| } |
| |
| expected_body = 'this is my data' |
| |
| self.assert_rewritten_response(expected_status, expected_headers, |
| expected_body, application) |
| |
| def test_cache_control_rewrite(self): |
| """Tests when the 'cache-control' header needs to be updated.""" |
| application = wsgi_test_utils.constant_app( |
| '200 OK', |
| [('Content-Type', 'text/html')], |
| 'this is my data') |
| |
| expected_status = '200 OK' |
| |
| expected_headers = { |
| 'Content-Type': 'text/html', |
| 'Cache-Control': 'no-cache', |
| 'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT', |
| 'Content-Length': '15', |
| } |
| |
| expected_body = 'this is my data' |
| |
| self.assert_rewritten_response(expected_status, expected_headers, |
| expected_body, application) |
| |
| def test_manual_cache_control(self): |
| """Tests that the user is able to manually set Cache-Control and Expires.""" |
| application = wsgi_test_utils.constant_app( |
| '200 OK', |
| [('Content-Type', 'text/html'), |
| ('Cache-Control', 'max-age'), |
| ('Expires', 'Mon, 25 Jul 9999 14:47:05 GMT'), |
| ], |
| 'this is my data') |
| |
| expected_status = '200 OK' |
| |
| expected_headers = { |
| 'Content-Type': 'text/html', |
| 'Cache-Control': 'max-age', |
| 'Expires': 'Mon, 25 Jul 9999 14:47:05 GMT', |
| 'Content-Length': '15', |
| } |
| |
| expected_body = 'this is my data' |
| |
| self.assert_rewritten_response(expected_status, expected_headers, |
| expected_body, application) |
| |
| def test_manual_cache_control_not_expires(self): |
| """Tests that the user is able to set Cache-Control without Expires.""" |
| application = wsgi_test_utils.constant_app( |
| '200 OK', |
| [('Content-Type', 'text/html'), |
| ('Cache-Control', 'max-age'), |
| ], |
| 'this is my data') |
| |
| expected_status = '200 OK' |
| |
| # Expect the custom Cache-Control, but no automatic Expires. |
| expected_headers = { |
| 'Content-Type': 'text/html', |
| 'Cache-Control': 'max-age', |
| 'Content-Length': '15', |
| } |
| |
| expected_body = 'this is my data' |
| |
| self.assert_rewritten_response(expected_status, expected_headers, |
| expected_body, application) |
| |
| def test_manual_expires(self): |
| """Tests that the user is able to set Expires without Cache-Control.""" |
| application = wsgi_test_utils.constant_app( |
| '200 OK', |
| [('Content-Type', 'text/html'), |
| ('Expires', 'Wed, 25 Jul 2012 14:47:05 GMT'), |
| ], |
| 'this is my data') |
| |
| expected_status = '200 OK' |
| |
| # Expect the default Cache-Control, and the custom Expires. |
| expected_headers = { |
| 'Content-Type': 'text/html', |
| 'Cache-Control': 'no-cache', |
| 'Expires': 'Wed, 25 Jul 2012 14:47:05 GMT', |
| 'Content-Length': '15', |
| } |
| |
| expected_body = 'this is my data' |
| |
| self.assert_rewritten_response(expected_status, expected_headers, |
| expected_body, application) |
| |
| def test_set_cookie_prevents_caching(self): |
| """Tests that the Set-Cookie header prevents caching from taking place.""" |
| # Test with no explicit Cache-Control and Expires headers. |
| m = mox.Mox() |
| m.StubOutWithMock(time, 'time') |
| time.time().AndReturn(788918400) # 1 Jan 1995 00:00:00 |
| m.ReplayAll() |
| |
| application = wsgi_test_utils.constant_app( |
| '200 OK', |
| [('Content-Type', 'text/html'), |
| ('Set-Cookie', 'UserID=john; Max-Age=3600; Version=1'), |
| ], |
| 'this is my data') |
| |
| expected_status = '200 OK' |
| |
| expected_headers = { |
| 'Content-Type': 'text/html', |
| 'Cache-Control': 'no-cache', |
| 'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT', |
| 'Content-length': '15', |
| 'Set-Cookie': 'UserID=john; Max-Age=3600; Version=1', |
| } |
| |
| expected_body = 'this is my data' |
| |
| self.assert_rewritten_response(expected_status, expected_headers, |
| expected_body, application) |
| |
| m.VerifyAll() |
| m.UnsetStubs() |
| |
| # Test with explicit Cache-Control: public and future Expires headers. |
| m = mox.Mox() |
| m.StubOutWithMock(time, 'time') |
| time.time().AndReturn(788918400) # 1 Jan 1995 00:00:00 |
| m.ReplayAll() |
| |
| application = wsgi_test_utils.constant_app( |
| '200 OK', |
| [('Content-Type', 'text/html'), |
| ('Cache-Control', 'public'), |
| ('Cache-Control', 'max-age=20'), |
| ('Expires', 'Mon, 25 Jul 9999 14:47:05 GMT'), |
| ('Set-Cookie', 'UserID=john; Max-Age=3600; Version=1'), |
| ], |
| 'this is my data') |
| |
| expected_status = '200 OK' |
| |
| expected_headers = { |
| 'Content-Type': 'text/html', |
| 'Cache-Control': 'max-age=20, private', |
| 'Expires': 'Sun, 01 Jan 1995 00:00:00 GMT', |
| 'Content-Length': '15', |
| 'Set-Cookie': 'UserID=john; Max-Age=3600; Version=1', |
| } |
| |
| expected_body = 'this is my data' |
| |
| self.assert_rewritten_response(expected_status, expected_headers, |
| expected_body, application) |
| |
| m.VerifyAll() |
| m.UnsetStubs() |
| |
| # Test with explicit Cache-Control: private and past Expires headers. |
| m = mox.Mox() |
| m.StubOutWithMock(time, 'time') |
| time.time().AndReturn(788918400) # 1 Jan 1995 00:00:00 |
| m.ReplayAll() |
| |
| application = wsgi_test_utils.constant_app( |
| '200 OK', |
| [('Content-Type', 'text/html'), |
| ('Cache-Control', 'private, max-age=20'), |
| ('Expires', 'Mon, 13 Mar 1992 18:12:51 GMT'), |
| ('Set-Cookie', 'UserID=john; Max-Age=3600; Version=1'), |
| ], |
| 'this is my data') |
| |
| expected_status = '200 OK' |
| |
| expected_headers = { |
| 'Content-Type': 'text/html', |
| 'Cache-Control': 'private, max-age=20', |
| 'Expires': 'Mon, 13 Mar 1992 18:12:51 GMT', |
| 'Content-Length': '15', |
| 'Set-Cookie': 'UserID=john; Max-Age=3600; Version=1', |
| } |
| |
| expected_body = 'this is my data' |
| |
| self.assert_rewritten_response(expected_status, expected_headers, |
| expected_body, application) |
| |
| m.VerifyAll() |
| m.UnsetStubs() |
| |
| # Test with a non-cacheable status code. |
| # Note: For simplicity, we simply treat this as normal (unlike production, |
| # which does not sanitize such cases). |
| m = mox.Mox() |
| m.StubOutWithMock(time, 'time') |
| time.time().AndReturn(788918400) # 1 Jan 1995 00:00:00 |
| m.ReplayAll() |
| |
| application = wsgi_test_utils.constant_app( |
| '404 Not Found', |
| [('Content-Type', 'text/html'), |
| ('Cache-Control', 'public, max-age=20'), |
| ('Expires', 'Mon, 25 Jul 9999 14:47:05 GMT'), |
| ('Set-Cookie', 'UserID=john; Max-Age=3600; Version=1'), |
| ], |
| 'this is my data') |
| |
| expected_status = '404 Not Found' |
| |
| expected_headers = { |
| 'Content-Type': 'text/html', |
| 'Cache-Control': 'max-age=20, private', |
| 'Expires': 'Sun, 01 Jan 1995 00:00:00 GMT', |
| 'Content-Length': '15', |
| 'Set-Cookie': 'UserID=john; Max-Age=3600; Version=1', |
| } |
| |
| expected_body = 'this is my data' |
| |
| self.assert_rewritten_response(expected_status, expected_headers, |
| expected_body, application) |
| |
| m.VerifyAll() |
| m.UnsetStubs() |
| |
| def _run_no_body_status_test(self, status): |
| """Tests rewriting when the status is not allowed to have a body.""" |
| application = wsgi_test_utils.constant_app( |
| status, |
| [('Content-Type', 'text/html'), |
| ('Content-Length', '1234'), |
| ], |
| 'this is my data') |
| |
| expected_status = status |
| |
| expected_headers = { |
| 'Content-Type': 'text/html', |
| } |
| |
| expected_body = '' |
| |
| self.assert_rewritten_response(expected_status, expected_headers, |
| expected_body, application) |
| |
| def test_no_body_100(self): |
| """Tests rewriting when the status is 100.""" |
| self._run_no_body_status_test('100 Continue') |
| |
| def test_no_body_101(self): |
| """Tests rewriting when the status is 101.""" |
| self._run_no_body_status_test('101 Switching Protocols') |
| |
| def test_no_body_204(self): |
| """Tests rewriting when the status is 204.""" |
| self._run_no_body_status_test('204 No Content') |
| |
| def test_no_body_304(self): |
| """Tests rewriting when the status is 304.""" |
| self._run_no_body_status_test('304 Not Modified') |
| |
| def test_no_rewrite(self): |
| """Tests when nothing gets rewritten.""" |
| application = wsgi_test_utils.constant_app( |
| '200 OK', |
| [('Content-Type', 'text/html'), |
| ('Cache-Control', 'no-cache'), |
| ('Expires', 'Fri, 01 Jan 1990 00:00:00 GMT'), |
| ('Content-Length', '15'), |
| ], |
| 'this is my data') |
| |
| expected_status = '200 OK' |
| |
| expected_headers = { |
| 'Content-Type': 'text/html', |
| 'Cache-Control': 'no-cache', |
| 'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT', |
| 'Content-Length': '15', |
| } |
| |
| expected_body = 'this is my data' |
| |
| self.assert_rewritten_response(expected_status, expected_headers, |
| expected_body, application) |
| |
| def test_header_sanitation(self): |
| """Tests that unsafe headers are deleted.""" |
| application = wsgi_test_utils.constant_app( |
| '200 OK', |
| [('Server', 'iis'), |
| ('Date', 'sometime in the summer'), |
| ('Content-Type', 'text/html'), |
| ('Cache-Control', 'no-cache'), |
| ('Expires', 'Fri, 01 Jan 1990 00:00:00 GMT'), |
| ('Content-Length', '1500'), |
| ('Content-Encoding', 'gzip'), |
| ('Accept-Encoding', 'gzip'), |
| ('Transfer-Encoding', 'chunked'), |
| ('Content-Disposition', 'attachment; filename="h\xc3\xa9llo.png"'), |
| ('Connection', 'close'), |
| ('Keep-Alive', 'foo'), |
| ('Proxy-Authenticate', 'Basic'), |
| ('Trailer', 'X-Bar'), |
| ('Upgrade', 'SPDY/2'), |
| # Detailed illegal character tests |
| ('has space', 'foo'), |
| ('has:colon', 'foo'), |
| ('has-non-printable\x03', 'foo'), |
| ('has-non-ascii\xdc', 'foo'), |
| ('value-has-space', 'has space'), # Legal |
| ('value-has-colon', 'has:colon'), # Legal |
| ('value-has-non-printable', 'ab\x03cd'), |
| ('value-has-non-ascii', 'ab\xdccd'), |
| ], |
| 'this is my data') |
| |
| expected_status = '200 OK' |
| |
| expected_headers = { |
| 'Content-Type': 'text/html', |
| 'Cache-Control': 'no-cache', |
| 'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT', |
| 'Content-Length': '15', |
| # NOTE: Accept-Encoding is not a response header, so we do not care if |
| # the user is able to set it. |
| 'Accept-Encoding': 'gzip', |
| 'value-has-space': 'has space', |
| 'value-has-colon': 'has:colon', |
| } |
| |
| expected_body = 'this is my data' |
| |
| self.assert_rewritten_response(expected_status, expected_headers, |
| expected_body, application) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |