Browse Source

Added HTTP exception trapping. This should fix #294

pull/296/merge
Armin Ronacher 13 years ago
parent
commit
7155f11a72
  1. 2
      CHANGES
  2. 20
      docs/config.rst
  3. 36
      flask/app.py
  4. 36
      tests/flask_tests.py

2
CHANGES

@ -14,6 +14,8 @@ Relase date to be decided, codename to be chosen.
- Empty session cookies are now deleted properly automatically.
- View functions can now opt out of getting the automatic
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
-------------

20
docs/config.rst

@ -81,6 +81,23 @@ The following configuration values are used internally by Flask:
reject incoming requests with a
content length greater than this by
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``
@ -114,6 +131,9 @@ The following configuration values are used internally by Flask:
.. versionadded:: 0.7
``PROPAGATE_EXCEPTIONS``, ``PRESERVE_CONTEXT_ON_EXCEPTION``
.. versionadded:: 0.8
``TRAP_BAD_REQUEST_KEY_ERRORS``, ``TRAP_HTTP_EXCEPTIONS``
Configuring from Files
----------------------

36
flask/app.py

@ -19,7 +19,7 @@ from itertools import chain
from werkzeug.datastructures import ImmutableDict
from werkzeug.routing import Map, Rule
from werkzeug.exceptions import HTTPException, InternalServerError, \
MethodNotAllowed
MethodNotAllowed, BadRequest
from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
locked_cached_property, _tojson_filter, _endpoint_from_view_func
@ -197,7 +197,9 @@ class Flask(_PackageBoundObject):
'USE_X_SENDFILE': False,
'LOGGER_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
@ -983,6 +985,24 @@ class Flask(_PackageBoundObject):
return 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):
"""This method is called whenever an exception occurs that should be
handled. A special case are
@ -993,14 +1013,16 @@ class Flask(_PackageBoundObject):
.. 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()
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 = ()
handlers = self.error_handler_spec.get(request.blueprint)
if handlers is not None:

36
tests/flask_tests.py

@ -23,7 +23,7 @@ from contextlib import contextmanager
from functools import update_wrapper
from datetime import datetime
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 jinja2 import TemplateNotFound
from cStringIO import StringIO
@ -592,6 +592,40 @@ class BasicFunctionalityTestCase(unittest.TestCase):
c = app.test_client()
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):
buffer = []
app = flask.Flask(__name__)

Loading…
Cancel
Save