Browse Source

Flask will now give you an error in debug mode if a post request caused a redirect by the routing system.

pull/302/head
Armin Ronacher 14 years ago
parent
commit
6847329134
  1. 20
      flask/app.py
  2. 26
      flask/debughelpers.py
  3. 24
      tests/flask_tests.py

20
flask/app.py

@ -18,7 +18,7 @@ from itertools import chain
from functools import update_wrapper from functools import update_wrapper
from werkzeug.datastructures import ImmutableDict from werkzeug.datastructures import ImmutableDict
from werkzeug.routing import Map, Rule from werkzeug.routing import Map, Rule, RequestRedirect
from werkzeug.exceptions import HTTPException, InternalServerError, \ from werkzeug.exceptions import HTTPException, InternalServerError, \
MethodNotAllowed, BadRequest MethodNotAllowed, BadRequest
@ -1134,6 +1134,22 @@ class Flask(_PackageBoundObject):
return InternalServerError() return InternalServerError()
return handler(e) return handler(e)
def raise_routing_exception(self, request):
"""Exceptions that are recording during routing are reraised with
this method. During debug we are not reraising redirect requests
for non ``GET``, ``HEAD``, or ``OPTIONS`` requests and we're raising
a different error instead to help debug situations.
:internal:
"""
if not self.debug \
or not isinstance(request.routing_exception, RequestRedirect) \
or request.method in ('GET', 'HEAD', 'OPTIONS'):
raise request.routing_exception
from .debughelpers import FormDataRoutingRedirect
raise FormDataRoutingRedirect(request)
def dispatch_request(self): def dispatch_request(self):
"""Does the request dispatching. Matches the URL and returns the """Does the request dispatching. Matches the URL and returns the
return value of the view or error handler. This does not have to return value of the view or error handler. This does not have to
@ -1146,7 +1162,7 @@ class Flask(_PackageBoundObject):
""" """
req = _request_ctx_stack.top.request req = _request_ctx_stack.top.request
if req.routing_exception is not None: if req.routing_exception is not None:
raise req.routing_exception self.raise_routing_exception(req)
rule = req.url_rule rule = req.url_rule
# if we provide automatic options for this URL and the # if we provide automatic options for this URL and the
# request came with the OPTIONS method, reply automatically # request came with the OPTIONS method, reply automatically

26
flask/debughelpers.py

@ -33,6 +33,32 @@ class DebugFilesKeyError(KeyError, AssertionError):
return self.msg return self.msg
class FormDataRoutingRedirect(AssertionError):
"""This exception is raised by Flask in debug mode if it detects a
redirect caused by the routing system when the request method is not
GET, HEAD or OPTIONS. Reasoning: form data will be dropped.
"""
def __init__(self, request):
exc = request.routing_exception
buf = ['A request was sent to this URL (%s) but a redirect was '
'issued automatically by the routing system to "%s".'
% (request.url, exc.new_url)]
# In case just a slash was appended we can be extra helpful
if request.base_url + '/' == exc.new_url.split('?')[0]:
buf.append(' The URL was defined with a trailing slash so '
'Flask will automatically redirect to the URL '
'with the trailing slash if it was accessed '
'without one.')
buf.append(' Make sure to directly send your %s-request to this URL '
'since we can\'t make browsers or HTTP clients redirect '
'with form data.' % request.method)
buf.append('\n\nNote: this exception is only raised in debug mode')
AssertionError.__init__(self, ''.join(buf).encode('utf-8'))
def attach_enctype_error_multidict(request): def attach_enctype_error_multidict(request):
"""Since Flask 0.8 we're monkeypatching the files object in case a """Since Flask 0.8 we're monkeypatching the files object in case a
request is detected that does not use multipart form data but the files request is detected that does not use multipart form data but the files

24
tests/flask_tests.py

@ -981,6 +981,30 @@ class BasicFunctionalityTestCase(unittest.TestCase):
self.assertEqual(got, [42]) self.assertEqual(got, [42])
self.assert_(app.got_first_request) self.assert_(app.got_first_request)
def test_routing_redirect_debugging(self):
app = flask.Flask(__name__)
app.debug = True
@app.route('/foo/', methods=['GET', 'POST'])
def foo():
return 'success'
with app.test_client() as c:
try:
c.post('/foo', data={})
except AssertionError, e:
self.assert_('http://localhost/foo/' in str(e))
self.assert_('Make sure to directly send your POST-request '
'to this URL' in str(e))
else:
self.fail('Expected exception')
rv = c.get('/foo', data={}, follow_redirects=True)
self.assertEqual(rv.data, 'success')
app.debug = False
with app.test_client() as c:
rv = c.post('/foo', data={}, follow_redirects=True)
self.assertEqual(rv.data, 'success')
class JSONTestCase(unittest.TestCase): class JSONTestCase(unittest.TestCase):

Loading…
Cancel
Save