Merge pull request #162 from lichray/default_options

feat(api): default responder for OPTIONS method

Closes issue #68
This commit is contained in:
Kurt Griffiths
2013-08-14 13:53:21 -07:00
4 changed files with 67 additions and 5 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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()