Merge pull request #162 from lichray/default_options
feat(api): default responder for OPTIONS method Closes issue #68
This commit is contained in:
@@ -203,6 +203,14 @@ def create_http_method_map(resource, uri_fields, before, after):
|
||||
|
||||
# Attach a resource for unsupported HTTP methods
|
||||
allowed_methods = sorted(list(method_map.keys()))
|
||||
|
||||
if 'OPTIONS' not in method_map:
|
||||
# OPTIONS itself is intentionally excluded from the Allow header
|
||||
# This default responder does not run the hooks
|
||||
method_map['OPTIONS'] = responders.create_default_options(
|
||||
allowed_methods)
|
||||
allowed_methods.append('OPTIONS')
|
||||
|
||||
na_responder = responders.create_method_not_allowed(allowed_methods)
|
||||
|
||||
for method in HTTP_METHODS:
|
||||
|
||||
@@ -16,6 +16,7 @@ limitations under the License.
|
||||
|
||||
"""
|
||||
|
||||
from falcon.status_codes import HTTP_204
|
||||
from falcon.status_codes import HTTP_400
|
||||
from falcon.status_codes import HTTP_404
|
||||
from falcon.status_codes import HTTP_405
|
||||
@@ -45,9 +46,27 @@ def create_method_not_allowed(allowed_methods):
|
||||
returned in the Allow header.
|
||||
|
||||
"""
|
||||
allowed = ', '.join(allowed_methods)
|
||||
|
||||
def method_not_allowed(req, resp, **kwargs):
|
||||
resp.status = HTTP_405
|
||||
resp.set_header('Allow', ', '.join(allowed_methods))
|
||||
resp.set_header('Allow', allowed)
|
||||
|
||||
return method_not_allowed
|
||||
|
||||
|
||||
def create_default_options(allowed_methods):
|
||||
"""Creates a default responder for the OPTIONS method
|
||||
|
||||
Args:
|
||||
allowed_methods: A list of HTTP methods (uppercase) that should be
|
||||
returned in the Allow header.
|
||||
|
||||
"""
|
||||
allowed = ', '.join(allowed_methods)
|
||||
|
||||
def on_options(req, resp, **kwargs):
|
||||
resp.status = HTTP_204
|
||||
resp.set_header('Allow', allowed)
|
||||
|
||||
return on_options
|
||||
|
||||
@@ -66,6 +66,12 @@ class ZooResource(object):
|
||||
self.resp = resp
|
||||
|
||||
|
||||
class SingleResource(object):
|
||||
|
||||
def on_options(self, req, resp):
|
||||
resp.status = falcon.HTTP_501
|
||||
|
||||
|
||||
class TestHooks(testing.TestBase):
|
||||
|
||||
def before(self):
|
||||
@@ -87,6 +93,11 @@ class TestHooks(testing.TestBase):
|
||||
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()
|
||||
@@ -122,3 +133,18 @@ class TestHooks(testing.TestBase):
|
||||
|
||||
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)
|
||||
|
||||
@@ -160,7 +160,7 @@ class TestHttpMethodRouting(testing.TestBase):
|
||||
|
||||
def test_methods_not_allowed_complex(self):
|
||||
for method in HTTP_METHODS:
|
||||
if method in ('GET', 'POST', 'HEAD'):
|
||||
if method in ('GET', 'POST', 'HEAD', 'OPTIONS'):
|
||||
continue
|
||||
|
||||
self.resource_things.called = False
|
||||
@@ -170,13 +170,13 @@ class TestHttpMethodRouting(testing.TestBase):
|
||||
self.assertEquals(self.srmock.status, falcon.HTTP_405)
|
||||
|
||||
headers = self.srmock.headers
|
||||
allow_header = ('Allow', 'GET, HEAD, POST')
|
||||
allow_header = ('Allow', 'GET, HEAD, POST, OPTIONS')
|
||||
|
||||
self.assertThat(headers, Contains(allow_header))
|
||||
|
||||
def test_method_not_allowed_with_param(self):
|
||||
for method in HTTP_METHODS:
|
||||
if method == 'GET' or method == 'PUT':
|
||||
if method in ('GET', 'PUT', 'OPTIONS'):
|
||||
continue
|
||||
|
||||
self.resource_get_with_faulty_put.called = False
|
||||
@@ -187,10 +187,19 @@ class TestHttpMethodRouting(testing.TestBase):
|
||||
self.assertEquals(self.srmock.status, falcon.HTTP_405)
|
||||
|
||||
headers = self.srmock.headers
|
||||
allow_header = ('Allow', 'GET, PUT')
|
||||
allow_header = ('Allow', 'GET, PUT, OPTIONS')
|
||||
|
||||
self.assertThat(headers, Contains(allow_header))
|
||||
|
||||
def test_default_on_options(self):
|
||||
self.simulate_request('/things/84/stuff/65', method='OPTIONS')
|
||||
self.assertEquals(self.srmock.status, falcon.HTTP_204)
|
||||
|
||||
headers = self.srmock.headers
|
||||
allow_header = ('Allow', 'GET, HEAD, POST')
|
||||
|
||||
self.assertThat(headers, Contains(allow_header))
|
||||
|
||||
def test_unexpected_type_error(self):
|
||||
# Suppress logging
|
||||
stream = io.StringIO() if six.PY3 else io.BytesIO()
|
||||
|
||||
Reference in New Issue
Block a user