-
-
Notifications
You must be signed in to change notification settings - Fork 925
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #502 from mcmasterathl/mcmaster/feature_414
feat(api) Add HTTPStatus exception for immediate response
- Loading branch information
Showing
3 changed files
with
260 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# Copyright 2015 by Hurricane Labs LLC | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
|
||
class HTTPStatus(Exception): | ||
"""Represents a generic HTTP status. | ||
Raise this class from a hook, middleware, or a responder to stop handling | ||
the request and skip to the response handling. | ||
Attributes: | ||
status (str): HTTP status line, e.g. '748 Confounded by Ponies'. | ||
headers (dict): Extra headers to add to the response. | ||
body (str or unicode): String representing response content. If | ||
Unicode, Falcon will encode as UTF-8 in the response. | ||
Args: | ||
status (str): HTTP status code and text, such as | ||
'748 Confounded by Ponies'. | ||
headers (dict): Extra headers to add to the response. | ||
body (str or unicode): String representing response content. If | ||
Unicode, Falcon will encode as UTF-8 in the response. | ||
""" | ||
|
||
__slots__ = ( | ||
'status', | ||
'headers', | ||
'body' | ||
) | ||
|
||
def __init__(self, status, headers=None, body=None): | ||
self.status = status | ||
self.headers = headers | ||
self.body = body |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
# -*- coding: utf-8 | ||
|
||
import falcon.testing as testing | ||
import falcon | ||
from falcon.http_status import HTTPStatus | ||
|
||
|
||
def before_hook(req, resp, params): | ||
raise HTTPStatus(falcon.HTTP_200, | ||
headers={"X-Failed": "False"}, | ||
body="Pass") | ||
|
||
|
||
def after_hook(req, resp, resource): | ||
resp.status = falcon.HTTP_200 | ||
resp.set_header("X-Failed", "False") | ||
resp.body = "Pass" | ||
|
||
|
||
def noop_after_hook(req, resp, resource): | ||
pass | ||
|
||
|
||
class TestStatusResource: | ||
|
||
@falcon.before(before_hook) | ||
def on_get(self, req, resp): | ||
resp.status = falcon.HTTP_500 | ||
resp.set_header("X-Failed", "True") | ||
resp.body = "Fail" | ||
|
||
def on_post(self, req, resp): | ||
resp.status = falcon.HTTP_500 | ||
resp.set_header("X-Failed", "True") | ||
resp.body = "Fail" | ||
|
||
raise HTTPStatus(falcon.HTTP_200, | ||
headers={"X-Failed": "False"}, | ||
body="Pass") | ||
|
||
@falcon.after(after_hook) | ||
def on_put(self, req, resp): | ||
resp.status = falcon.HTTP_500 | ||
resp.set_header("X-Failed", "True") | ||
resp.body = "Fail" | ||
|
||
def on_patch(self, req, resp): | ||
raise HTTPStatus(falcon.HTTP_200, | ||
body=None) | ||
|
||
@falcon.after(noop_after_hook) | ||
def on_delete(self, req, resp): | ||
raise HTTPStatus(falcon.HTTP_200, | ||
headers={"X-Failed": "False"}, | ||
body="Pass") | ||
|
||
|
||
class TestHookResource: | ||
|
||
def on_get(self, req, resp): | ||
resp.status = falcon.HTTP_500 | ||
resp.set_header("X-Failed", "True") | ||
resp.body = "Fail" | ||
|
||
def on_patch(self, req, resp): | ||
raise HTTPStatus(falcon.HTTP_200, | ||
body=None) | ||
|
||
def on_delete(self, req, resp): | ||
raise HTTPStatus(falcon.HTTP_200, | ||
headers={"X-Failed": "False"}, | ||
body="Pass") | ||
|
||
|
||
class TestHTTPStatus(testing.TestBase): | ||
def before(self): | ||
self.resource = TestStatusResource() | ||
self.api.add_route('/status', self.resource) | ||
|
||
def test_raise_status_in_before_hook(self): | ||
""" Make sure we get the 200 raised by before hook """ | ||
body = self.simulate_request('/status', method='GET', decode='utf-8') | ||
self.assertEqual(self.srmock.status, falcon.HTTP_200) | ||
self.assertIn(('x-failed', 'False'), self.srmock.headers) | ||
self.assertEqual(body, 'Pass') | ||
|
||
def test_raise_status_in_responder(self): | ||
""" Make sure we get the 200 raised by responder """ | ||
body = self.simulate_request('/status', method='POST', decode='utf-8') | ||
self.assertEqual(self.srmock.status, falcon.HTTP_200) | ||
self.assertIn(('x-failed', 'False'), self.srmock.headers) | ||
self.assertEqual(body, 'Pass') | ||
|
||
def test_raise_status_runs_after_hooks(self): | ||
""" Make sure after hooks still run """ | ||
body = self.simulate_request('/status', method='PUT', decode='utf-8') | ||
self.assertEqual(self.srmock.status, falcon.HTTP_200) | ||
self.assertIn(('x-failed', 'False'), self.srmock.headers) | ||
self.assertEqual(body, 'Pass') | ||
|
||
def test_raise_status_survives_after_hooks(self): | ||
""" Make sure after hook doesn't overwrite our status """ | ||
body = self.simulate_request('/status', method='DELETE', | ||
decode='utf-8') | ||
self.assertEqual(self.srmock.status, falcon.HTTP_200) | ||
self.assertIn(('x-failed', 'False'), self.srmock.headers) | ||
self.assertEqual(body, 'Pass') | ||
|
||
def test_raise_status_empty_body(self): | ||
""" Make sure passing None to body results in empty body """ | ||
body = self.simulate_request('/status', method='PATCH', decode='utf-8') | ||
self.assertEqual(body, '') | ||
|
||
|
||
class TestHTTPStatusWithGlobalHooks(testing.TestBase): | ||
def before(self): | ||
self.resource = TestHookResource() | ||
|
||
def test_raise_status_in_before_hook(self): | ||
""" Make sure we get the 200 raised by before hook """ | ||
self.api = falcon.API(before=[before_hook]) | ||
self.api.add_route('/status', self.resource) | ||
|
||
body = self.simulate_request('/status', method='GET', decode='utf-8') | ||
self.assertEqual(self.srmock.status, falcon.HTTP_200) | ||
self.assertIn(('x-failed', 'False'), self.srmock.headers) | ||
self.assertEqual(body, 'Pass') | ||
|
||
def test_raise_status_runs_after_hooks(self): | ||
""" Make sure we still run after hooks """ | ||
self.api = falcon.API(after=[after_hook]) | ||
self.api.add_route('/status', self.resource) | ||
|
||
body = self.simulate_request('/status', method='GET', decode='utf-8') | ||
self.assertEqual(self.srmock.status, falcon.HTTP_200) | ||
self.assertIn(('x-failed', 'False'), self.srmock.headers) | ||
self.assertEqual(body, 'Pass') | ||
|
||
def test_raise_status_survives_after_hooks(self): | ||
""" Make sure after hook doesn't overwrite our status """ | ||
self.api = falcon.API(after=[noop_after_hook]) | ||
self.api.add_route('/status', self.resource) | ||
|
||
body = self.simulate_request('/status', method='DELETE', | ||
decode='utf-8') | ||
self.assertEqual(self.srmock.status, falcon.HTTP_200) | ||
self.assertIn(('x-failed', 'False'), self.srmock.headers) | ||
self.assertEqual(body, 'Pass') | ||
|
||
def test_raise_status_in_process_request(self): | ||
""" Make sure we can raise status from middleware process request """ | ||
class TestMiddleware: | ||
def process_request(self, req, resp): | ||
raise HTTPStatus(falcon.HTTP_200, | ||
headers={"X-Failed": "False"}, | ||
body="Pass") | ||
|
||
self.api = falcon.API(middleware=TestMiddleware()) | ||
self.api.add_route('/status', self.resource) | ||
|
||
body = self.simulate_request('/status', method='GET', decode='utf-8') | ||
self.assertEqual(self.srmock.status, falcon.HTTP_200) | ||
self.assertIn(('x-failed', 'False'), self.srmock.headers) | ||
self.assertEqual(body, 'Pass') | ||
|
||
def test_raise_status_in_process_resource(self): | ||
""" Make sure we can raise status from middleware process resource """ | ||
class TestMiddleware: | ||
def process_resource(self, req, resp, resource): | ||
raise HTTPStatus(falcon.HTTP_200, | ||
headers={"X-Failed": "False"}, | ||
body="Pass") | ||
|
||
self.api = falcon.API(middleware=TestMiddleware()) | ||
self.api.add_route('/status', self.resource) | ||
|
||
body = self.simulate_request('/status', method='GET', decode='utf-8') | ||
self.assertEqual(self.srmock.status, falcon.HTTP_200) | ||
self.assertIn(('x-failed', 'False'), self.srmock.headers) | ||
self.assertEqual(body, 'Pass') | ||
|
||
def test_raise_status_runs_process_response(self): | ||
""" Make sure process_response still runs """ | ||
class TestMiddleware: | ||
def process_response(self, req, resp, response): | ||
resp.status = falcon.HTTP_200 | ||
resp.set_header("X-Failed", "False") | ||
resp.body = "Pass" | ||
|
||
self.api = falcon.API(middleware=TestMiddleware()) | ||
self.api.add_route('/status', self.resource) | ||
|
||
body = self.simulate_request('/status', method='GET', decode='utf-8') | ||
self.assertEqual(self.srmock.status, falcon.HTTP_200) | ||
self.assertIn(('x-failed', 'False'), self.srmock.headers) | ||
self.assertEqual(body, 'Pass') |