From f30deb8fea1e6a23855e61a9f7ee5b7aae0c4666 Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Tue, 27 Aug 2013 16:17:34 -0400 Subject: [PATCH] feat(api): set_default_route as a catch-all route A default route which is triggered when no route is found for a request. The change simplifies implementing a proxy-like app. --- falcon/api.py | 33 ++++++++++++++-- falcon/tests/test_default_routing.py | 57 ++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 falcon/tests/test_default_routing.py diff --git a/falcon/api.py b/falcon/api.py index ab964b8..2ba12fd 100644 --- a/falcon/api.py +++ b/falcon/api.py @@ -37,7 +37,8 @@ class API(object): """ - __slots__ = ('_after', '_before', '_media_type', '_routes') + __slots__ = ('_after', '_before', '_media_type', '_routes', + '_default_route') def __init__(self, media_type=DEFAULT_MEDIA_TYPE, before=None, after=None): """Initialize a new Falcon API instances @@ -57,6 +58,7 @@ class API(object): """ self._routes = [] + self._default_route = None self._media_type = media_type self._before = helpers.prepare_global_hooks(before) @@ -212,6 +214,21 @@ class API(object): # adds (will cause the last one to win). self._routes.insert(0, (path_template, method_map, na_responder)) + def set_default_route(self, default_resource): + """Route all the unrouted requests to a default resource + + Args: + default_resource: Object which works like an HTTP/REST resource. + Falcon will pass "GET" requests to on_get, "PUT" requests to + on_put, etc. If you want to exclude some HTTP method from the + default routing, just simply don't define the corresponding + request handlers. + + """ + + self._default_route = helpers.create_http_method_map( + default_resource, set(), self._before, self._after) + #---------------------------------------------------------------------------- # Helpers #---------------------------------------------------------------------------- @@ -242,8 +259,18 @@ class API(object): break else: - responder = falcon.responders.path_not_found params = {} - na_responder = falcon.responders.create_method_not_allowed([]) + + if self._default_route is not None: + method_map, na_responder = self._default_route + + try: + responder = method_map[method] + except KeyError: + responder = falcon.responders.bad_request + + else: + responder = falcon.responders.path_not_found + na_responder = falcon.responders.create_method_not_allowed([]) return (responder, params, na_responder) diff --git a/falcon/tests/test_default_routing.py b/falcon/tests/test_default_routing.py new file mode 100644 index 0000000..1c1d4f1 --- /dev/null +++ b/falcon/tests/test_default_routing.py @@ -0,0 +1,57 @@ +from testtools.matchers import Contains + +import falcon +import falcon.testing as testing + + +class HumanResource(object): + def on_delete(self, req, resp, name): + resp.status = falcon.HTTP_204 + + def on_get(self, req, resp, name): + resp.status = falcon.HTTP_402 + + +class UndeadResource(object): + def on_get(self, req, resp): + resp.status = falcon.HTTP_200 + + +class TestDefaultRouting(testing.TestBase): + + def before(self): + self.default_resource = UndeadResource() + self.resource = HumanResource() + + def test_default_only(self): + self.api.set_default_route(self.default_resource) + + self.simulate_request('/') + self.assertEquals(self.srmock.status, falcon.HTTP_200) + + self.simulate_request('/any') + self.assertEquals(self.srmock.status, falcon.HTTP_200) + + def test_routing_prioritise(self): + self.api.set_default_route(self.default_resource) + self.api.add_route('/people/{name}', self.resource) + + self.simulate_request('/people/asuka') + self.assertEquals(self.srmock.status, falcon.HTTP_402) + + self.simulate_request('/person/asuka') + self.assertEquals(self.srmock.status, falcon.HTTP_200) + + self.simulate_request('/people/asuka', method='DELETE') + self.assertEquals(self.srmock.status, falcon.HTTP_204) + + self.simulate_request('/person/asuka', method='DELETE') + self.assertEquals(self.srmock.status, falcon.HTTP_405) + + headers = self.srmock.headers + allow_header = ('Allow', 'GET, OPTIONS') + + self.assertThat(headers, Contains(allow_header)) + + self.simulate_request('/person/asuka', method=self.getUniqueString()) + self.assertEquals(self.srmock.status, falcon.HTTP_400)