Web developers tend to think of headers being case-insensitive. However, if you try to set the same header twice, with differently-cased header names, Falcon treated them as separate headers. This patch changes falcon.Request such that it now normalizes header names internally, and when performing lookups. Applications shouldn't break due to this change unless they were accessing the private req._headers attribute directly. Closes #185
167 lines
5.4 KiB
Python
167 lines
5.4 KiB
Python
from testtools.matchers import Contains
|
|
|
|
import falcon
|
|
import io
|
|
import falcon.testing as testing
|
|
|
|
import six
|
|
|
|
|
|
class HelloResource:
|
|
sample_status = '200 OK'
|
|
sample_unicode = (u'Hello World! \x80' +
|
|
six.text_type(testing.rand_string(0, 0)))
|
|
|
|
sample_utf8 = sample_unicode.encode('utf-8')
|
|
|
|
def __init__(self, mode):
|
|
self.called = False
|
|
self.mode = mode
|
|
|
|
def on_get(self, req, resp):
|
|
self.called = True
|
|
|
|
self.req, self.resp = req, resp
|
|
|
|
resp.status = falcon.HTTP_200
|
|
|
|
if 'stream' in self.mode:
|
|
resp.stream = io.BytesIO(self.sample_utf8)
|
|
|
|
if 'stream_len' in self.mode:
|
|
resp.stream_len = len(self.sample_utf8)
|
|
|
|
if 'body' in self.mode:
|
|
if 'bytes' in self.mode:
|
|
resp.body = self.sample_utf8
|
|
else:
|
|
resp.body = self.sample_unicode
|
|
|
|
if 'data' in self.mode:
|
|
resp.data = self.sample_utf8
|
|
|
|
def on_head(self, req, resp):
|
|
self.on_get(req, resp)
|
|
|
|
|
|
class NoStatusResource:
|
|
def on_get(self, req, resp):
|
|
pass
|
|
|
|
|
|
class TestHelloWorld(testing.TestBase):
|
|
|
|
def before(self):
|
|
self.resource = HelloResource('body')
|
|
self.api.add_route(self.test_route, self.resource)
|
|
|
|
self.bytes_resource = HelloResource('body, bytes')
|
|
self.api.add_route('/bytes', self.bytes_resource)
|
|
|
|
self.data_resource = HelloResource('data')
|
|
self.api.add_route('/data', self.data_resource)
|
|
|
|
self.chunked_resource = HelloResource('stream')
|
|
self.api.add_route('/chunked-stream', self.chunked_resource)
|
|
|
|
self.stream_resource = HelloResource('stream, stream_len')
|
|
self.api.add_route('/stream', self.stream_resource)
|
|
|
|
self.no_status_resource = NoStatusResource()
|
|
self.api.add_route('/nostatus', self.no_status_resource)
|
|
|
|
self.root_resource = testing.TestResource()
|
|
self.api.add_route('/', self.root_resource)
|
|
|
|
def after(self):
|
|
pass
|
|
|
|
def test_env_headers_list_of_tuples(self):
|
|
env = testing.create_environ(headers=[('User-Agent', 'Falcon-Test')])
|
|
self.assertEqual(env['HTTP_USER_AGENT'], 'Falcon-Test')
|
|
|
|
def test_empty_route(self):
|
|
self.simulate_request('')
|
|
self.assertTrue(self.root_resource.called)
|
|
|
|
def test_route_negative(self):
|
|
bogus_route = self.test_route + 'x'
|
|
self.simulate_request(bogus_route)
|
|
|
|
# Ensure the request was NOT routed to resource
|
|
self.assertFalse(self.resource.called)
|
|
self.assertEqual(self.srmock.status, falcon.HTTP_404)
|
|
|
|
def test_body(self):
|
|
body = self.simulate_request(self.test_route)
|
|
resp = self.resource.resp
|
|
|
|
content_length = int(self.srmock.headers_dict['content-length'])
|
|
self.assertEqual(content_length, len(self.resource.sample_utf8))
|
|
|
|
self.assertEqual(self.srmock.status, self.resource.sample_status)
|
|
self.assertEqual(resp.status, self.resource.sample_status)
|
|
self.assertEqual(resp.body_encoded, self.resource.sample_utf8)
|
|
self.assertEqual(body, [self.resource.sample_utf8])
|
|
|
|
def test_body_bytes(self):
|
|
body = self.simulate_request('/bytes')
|
|
resp = self.bytes_resource.resp
|
|
|
|
content_length = int(self.srmock.headers_dict['content-length'])
|
|
self.assertEqual(content_length, len(self.resource.sample_utf8))
|
|
|
|
self.assertEqual(self.srmock.status, self.resource.sample_status)
|
|
self.assertEqual(resp.status, self.resource.sample_status)
|
|
self.assertEqual(resp.body_encoded, self.resource.sample_utf8)
|
|
self.assertEqual(body, [self.resource.sample_utf8])
|
|
|
|
def test_data(self):
|
|
body = self.simulate_request('/data')
|
|
resp = self.data_resource.resp
|
|
|
|
content_length = int(self.srmock.headers_dict['content-length'])
|
|
self.assertEqual(content_length, len(self.resource.sample_utf8))
|
|
|
|
self.assertEqual(self.srmock.status, self.resource.sample_status)
|
|
self.assertEqual(resp.status, self.resource.sample_status)
|
|
self.assertEqual(resp.data, self.resource.sample_utf8)
|
|
self.assertEqual(body, [self.resource.sample_utf8])
|
|
|
|
def test_no_body_on_head(self):
|
|
body = self.simulate_request(self.test_route, method='HEAD')
|
|
self.assertEqual(body, [])
|
|
self.assertEqual(self.srmock.status, falcon.HTTP_200)
|
|
|
|
def test_stream_chunked(self):
|
|
src = self.simulate_request('/chunked-stream')
|
|
|
|
dest = io.BytesIO()
|
|
for chunk in src:
|
|
dest.write(chunk)
|
|
|
|
self.assertEqual(dest.getvalue(), self.chunked_resource.sample_utf8)
|
|
|
|
for header in self.srmock.headers:
|
|
self.assertNotEqual(header[0].lower(), 'content-length')
|
|
|
|
def test_stream_known_len(self):
|
|
src = self.simulate_request('/stream')
|
|
|
|
dest = io.BytesIO()
|
|
for chunk in src:
|
|
dest.write(chunk)
|
|
|
|
expected_len = self.stream_resource.resp.stream_len
|
|
content_length = ('content-length', str(expected_len))
|
|
self.assertThat(self.srmock.headers, Contains(content_length))
|
|
self.assertEqual(dest.tell(), expected_len)
|
|
|
|
self.assertEqual(dest.getvalue(), self.chunked_resource.sample_utf8)
|
|
|
|
def test_status_not_set(self):
|
|
body = self.simulate_request('/nostatus')
|
|
|
|
self.assertEqual(body, [])
|
|
self.assertEqual(self.srmock.status, falcon.HTTP_200)
|