Added HTTP exception trapping. This should fix #294
This commit is contained in:
parent
a070b4fe86
commit
7155f11a72
4 changed files with 86 additions and 8 deletions
2
CHANGES
2
CHANGES
|
|
@ -14,6 +14,8 @@ Relase date to be decided, codename to be chosen.
|
||||||
- Empty session cookies are now deleted properly automatically.
|
- Empty session cookies are now deleted properly automatically.
|
||||||
- View functions can now opt out of getting the automatic
|
- View functions can now opt out of getting the automatic
|
||||||
OPTIONS implementation.
|
OPTIONS implementation.
|
||||||
|
- HTTP exceptions and Bad Request Key Errors can now be trapped so that they
|
||||||
|
show up normally in the traceback.
|
||||||
|
|
||||||
Version 0.7.3
|
Version 0.7.3
|
||||||
-------------
|
-------------
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,23 @@ The following configuration values are used internally by Flask:
|
||||||
reject incoming requests with a
|
reject incoming requests with a
|
||||||
content length greater than this by
|
content length greater than this by
|
||||||
returning a 413 status code.
|
returning a 413 status code.
|
||||||
|
``TRAP_HTTP_EXCEPTIONS`` If this is set to ``True`` Flask will
|
||||||
|
not execute the error handlers of HTTP
|
||||||
|
exceptions but instead treat the
|
||||||
|
exception like any other and bubble it
|
||||||
|
through the exception stack. This is
|
||||||
|
helpful for hairy debugging situations
|
||||||
|
where you have to find out where an HTTP
|
||||||
|
exception is coming from.
|
||||||
|
``TRAP_BAD_REQUEST_KEY_ERRORS`` Werkzeug's internal data structures that
|
||||||
|
deal with request specific data will
|
||||||
|
raise special key errors that are also
|
||||||
|
bad request exceptions. By default
|
||||||
|
these will be converted into 400
|
||||||
|
responses which however can make
|
||||||
|
debugging some issues harder. If this
|
||||||
|
config is set to ``True`` you will get
|
||||||
|
a regular traceback instead.
|
||||||
================================= =========================================
|
================================= =========================================
|
||||||
|
|
||||||
.. admonition:: More on ``SERVER_NAME``
|
.. admonition:: More on ``SERVER_NAME``
|
||||||
|
|
@ -114,6 +131,9 @@ The following configuration values are used internally by Flask:
|
||||||
.. versionadded:: 0.7
|
.. versionadded:: 0.7
|
||||||
``PROPAGATE_EXCEPTIONS``, ``PRESERVE_CONTEXT_ON_EXCEPTION``
|
``PROPAGATE_EXCEPTIONS``, ``PRESERVE_CONTEXT_ON_EXCEPTION``
|
||||||
|
|
||||||
|
.. versionadded:: 0.8
|
||||||
|
``TRAP_BAD_REQUEST_KEY_ERRORS``, ``TRAP_HTTP_EXCEPTIONS``
|
||||||
|
|
||||||
Configuring from Files
|
Configuring from Files
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|
|
||||||
36
flask/app.py
36
flask/app.py
|
|
@ -19,7 +19,7 @@ from itertools import chain
|
||||||
from werkzeug.datastructures import ImmutableDict
|
from werkzeug.datastructures import ImmutableDict
|
||||||
from werkzeug.routing import Map, Rule
|
from werkzeug.routing import Map, Rule
|
||||||
from werkzeug.exceptions import HTTPException, InternalServerError, \
|
from werkzeug.exceptions import HTTPException, InternalServerError, \
|
||||||
MethodNotAllowed
|
MethodNotAllowed, BadRequest
|
||||||
|
|
||||||
from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
|
from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
|
||||||
locked_cached_property, _tojson_filter, _endpoint_from_view_func
|
locked_cached_property, _tojson_filter, _endpoint_from_view_func
|
||||||
|
|
@ -197,7 +197,9 @@ class Flask(_PackageBoundObject):
|
||||||
'USE_X_SENDFILE': False,
|
'USE_X_SENDFILE': False,
|
||||||
'LOGGER_NAME': None,
|
'LOGGER_NAME': None,
|
||||||
'SERVER_NAME': None,
|
'SERVER_NAME': None,
|
||||||
'MAX_CONTENT_LENGTH': None
|
'MAX_CONTENT_LENGTH': None,
|
||||||
|
'TRAP_BAD_REQUEST_KEY_ERRORS': False,
|
||||||
|
'TRAP_HTTP_EXCEPTIONS': False
|
||||||
})
|
})
|
||||||
|
|
||||||
#: The rule object to use for URL rules created. This is used by
|
#: The rule object to use for URL rules created. This is used by
|
||||||
|
|
@ -983,6 +985,24 @@ class Flask(_PackageBoundObject):
|
||||||
return e
|
return e
|
||||||
return handler(e)
|
return handler(e)
|
||||||
|
|
||||||
|
def trap_http_exception(self, e):
|
||||||
|
"""Checks if an HTTP exception should be trapped or not. By default
|
||||||
|
this will return `False` for all exceptions except for a bad request
|
||||||
|
key error if ``TRAP_BAD_REQUEST_KEY_ERRORS`` is set to `True`. It
|
||||||
|
also returns `True` if ``TRAP_HTTP_EXCEPTIONS`` is set to `True`.
|
||||||
|
|
||||||
|
This is called for all HTTP exceptions raised by a view function.
|
||||||
|
If it returns `True` for any exception the error handler for this
|
||||||
|
exception is not called and it shows up as regular exception in the
|
||||||
|
traceback. This is helpful for debugging implicitly raised HTTP
|
||||||
|
exceptions.
|
||||||
|
"""
|
||||||
|
if self.config['TRAP_HTTP_EXCEPTIONS']:
|
||||||
|
return True
|
||||||
|
if self.config['TRAP_BAD_REQUEST_KEY_ERRORS']:
|
||||||
|
return isinstance(e, BadRequest) and isinstance(e, LookupError)
|
||||||
|
return False
|
||||||
|
|
||||||
def handle_user_exception(self, e):
|
def handle_user_exception(self, e):
|
||||||
"""This method is called whenever an exception occurs that should be
|
"""This method is called whenever an exception occurs that should be
|
||||||
handled. A special case are
|
handled. A special case are
|
||||||
|
|
@ -993,14 +1013,16 @@ class Flask(_PackageBoundObject):
|
||||||
|
|
||||||
.. versionadded:: 0.7
|
.. versionadded:: 0.7
|
||||||
"""
|
"""
|
||||||
# ensure not to trash sys.exc_info() at that point in case someone
|
|
||||||
# wants the traceback preserved in handle_http_exception.
|
|
||||||
if isinstance(e, HTTPException):
|
|
||||||
return self.handle_http_exception(e)
|
|
||||||
|
|
||||||
exc_type, exc_value, tb = sys.exc_info()
|
exc_type, exc_value, tb = sys.exc_info()
|
||||||
assert exc_value is e
|
assert exc_value is e
|
||||||
|
|
||||||
|
# ensure not to trash sys.exc_info() at that point in case someone
|
||||||
|
# wants the traceback preserved in handle_http_exception. Of course
|
||||||
|
# we cannot prevent users from trashing it themselves in a custom
|
||||||
|
# trap_http_exception method so that's their fault then.
|
||||||
|
if isinstance(e, HTTPException) and not self.trap_http_exception(e):
|
||||||
|
return self.handle_http_exception(e)
|
||||||
|
|
||||||
blueprint_handlers = ()
|
blueprint_handlers = ()
|
||||||
handlers = self.error_handler_spec.get(request.blueprint)
|
handlers = self.error_handler_spec.get(request.blueprint)
|
||||||
if handlers is not None:
|
if handlers is not None:
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ from contextlib import contextmanager
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from werkzeug import parse_date, parse_options_header
|
from werkzeug import parse_date, parse_options_header
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound, BadRequest
|
||||||
from werkzeug.http import parse_set_header
|
from werkzeug.http import parse_set_header
|
||||||
from jinja2 import TemplateNotFound
|
from jinja2 import TemplateNotFound
|
||||||
from cStringIO import StringIO
|
from cStringIO import StringIO
|
||||||
|
|
@ -592,6 +592,40 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
c = app.test_client()
|
c = app.test_client()
|
||||||
assert c.get('/').data == '42'
|
assert c.get('/').data == '42'
|
||||||
|
|
||||||
|
def test_trapping_of_bad_request_key_errors(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.testing = True
|
||||||
|
@app.route('/fail')
|
||||||
|
def fail():
|
||||||
|
flask.request.form['missing_key']
|
||||||
|
c = app.test_client()
|
||||||
|
assert c.get('/fail').status_code == 400
|
||||||
|
|
||||||
|
app.config['TRAP_BAD_REQUEST_KEY_ERRORS'] = True
|
||||||
|
c = app.test_client()
|
||||||
|
try:
|
||||||
|
c.get('/fail')
|
||||||
|
except KeyError, e:
|
||||||
|
assert isinstance(e, BadRequest)
|
||||||
|
else:
|
||||||
|
self.fail('Expected exception')
|
||||||
|
|
||||||
|
def test_trapping_of_all_http_exceptions(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.testing = True
|
||||||
|
app.config['TRAP_HTTP_EXCEPTIONS'] = True
|
||||||
|
@app.route('/fail')
|
||||||
|
def fail():
|
||||||
|
flask.abort(404)
|
||||||
|
|
||||||
|
c = app.test_client()
|
||||||
|
try:
|
||||||
|
c.get('/fail')
|
||||||
|
except NotFound, e:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.fail('Expected exception')
|
||||||
|
|
||||||
def test_teardown_on_pop(self):
|
def test_teardown_on_pop(self):
|
||||||
buffer = []
|
buffer = []
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue