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
151 lines
4.2 KiB
Python
151 lines
4.2 KiB
Python
import json
|
|
|
|
import falcon
|
|
import falcon.testing as testing
|
|
|
|
|
|
def validate_output(req, resp):
|
|
raise falcon.HTTPError(falcon.HTTP_723, title=None)
|
|
|
|
|
|
def serialize_body(req, resp):
|
|
body = resp.body
|
|
if body is not None:
|
|
resp.body = json.dumps(body)
|
|
else:
|
|
resp.body = 'Nothing to see here. Move along.'
|
|
|
|
|
|
def fluffiness(req, resp):
|
|
resp.body = 'fluffy'
|
|
|
|
|
|
def cuteness(req, resp):
|
|
if resp.body == 'fluffy':
|
|
resp.body += ' and cute'
|
|
|
|
|
|
class WrappedRespondersResource(object):
|
|
|
|
@falcon.after(serialize_body)
|
|
@falcon.after(validate_output)
|
|
def on_get(self, req, resp):
|
|
self.req = req
|
|
self.resp = resp
|
|
|
|
@falcon.after(serialize_body)
|
|
def on_put(self, req, resp):
|
|
self.req = req
|
|
self.resp = resp
|
|
resp.body = {'animal': 'falcon'}
|
|
|
|
|
|
@falcon.after(cuteness)
|
|
@falcon.after(fluffiness)
|
|
class WrappedClassResource(object):
|
|
|
|
# Test that the decorator skips non-callables
|
|
on_post = False
|
|
|
|
def __init__(self):
|
|
# Test that the decorator skips non-callables
|
|
self.on_patch = []
|
|
|
|
def on_get(self, req, resp):
|
|
self.req = req
|
|
self.resp = resp
|
|
|
|
def on_head(self, req, resp):
|
|
self.req = req
|
|
self.resp = resp
|
|
|
|
|
|
class ZooResource(object):
|
|
|
|
def on_get(self, req, resp):
|
|
self.resp = resp
|
|
|
|
|
|
class SingleResource(object):
|
|
|
|
def on_options(self, req, resp):
|
|
resp.status = falcon.HTTP_501
|
|
|
|
|
|
class TestHooks(testing.TestBase):
|
|
|
|
def before(self):
|
|
self.resource = WrappedRespondersResource()
|
|
self.api.add_route(self.test_route, self.resource)
|
|
|
|
self.wrapped_resource = WrappedClassResource()
|
|
self.api.add_route('/wrapped', self.wrapped_resource)
|
|
|
|
def test_global_hook(self):
|
|
self.assertRaises(TypeError, falcon.API, None, {})
|
|
self.assertRaises(TypeError, falcon.API, None, 0)
|
|
|
|
self.api = falcon.API(after=fluffiness)
|
|
zoo_resource = ZooResource()
|
|
|
|
self.api.add_route(self.test_route, zoo_resource)
|
|
|
|
self.simulate_request(self.test_route)
|
|
self.assertEqual(b'fluffy', zoo_resource.resp.body_encoded)
|
|
|
|
# hook does not affect the default on_options
|
|
body = self.simulate_request(self.test_route, method='OPTIONS')
|
|
self.assertEqual(falcon.HTTP_204, self.srmock.status)
|
|
self.assertEqual([], body)
|
|
|
|
def test_multiple_global_hook(self):
|
|
self.api = falcon.API(after=[fluffiness, cuteness])
|
|
zoo_resource = ZooResource()
|
|
|
|
self.api.add_route(self.test_route, zoo_resource)
|
|
|
|
self.simulate_request(self.test_route)
|
|
self.assertEqual(b'fluffy and cute', zoo_resource.resp.body_encoded)
|
|
|
|
def test_output_validator(self):
|
|
self.simulate_request(self.test_route)
|
|
self.assertEqual(falcon.HTTP_723, self.srmock.status)
|
|
self.assertEqual(None, self.resource.resp.body_encoded)
|
|
|
|
def test_serializer(self):
|
|
self.simulate_request(self.test_route, method='PUT')
|
|
|
|
actual_body = self.resource.resp.body_encoded
|
|
self.assertEqual(b'{"animal": "falcon"}', actual_body)
|
|
|
|
def test_wrapped_resource(self):
|
|
expected = b'fluffy and cute'
|
|
|
|
self.simulate_request('/wrapped')
|
|
self.assertEqual(falcon.HTTP_200, self.srmock.status)
|
|
self.assertEqual(expected, self.wrapped_resource.resp.body_encoded)
|
|
|
|
self.simulate_request('/wrapped', method='HEAD')
|
|
self.assertEqual(falcon.HTTP_200, self.srmock.status)
|
|
|
|
self.simulate_request('/wrapped', method='POST')
|
|
self.assertEqual(falcon.HTTP_405, self.srmock.status)
|
|
|
|
self.simulate_request('/wrapped', method='PATCH')
|
|
self.assertEqual(falcon.HTTP_405, self.srmock.status)
|
|
|
|
# decorator does not affect the default on_options
|
|
body = self.simulate_request('/wrapped', method='OPTIONS')
|
|
self.assertEqual(falcon.HTTP_204, self.srmock.status)
|
|
self.assertEqual([], body)
|
|
|
|
def test_customized_options(self):
|
|
self.api = falcon.API(after=fluffiness)
|
|
|
|
self.api.add_route('/one', SingleResource())
|
|
|
|
body = self.simulate_request('/one', method='OPTIONS')
|
|
self.assertEqual(falcon.HTTP_501, self.srmock.status)
|
|
self.assertEqual([b'fluffy'], body)
|
|
self.assertNotIn('allow', self.srmock.headers_dict)
|