Flask will now give you an error in debug mode if a post request caused a redirect by the routing system.
This commit is contained in:
parent
ce70131975
commit
6847329134
3 changed files with 68 additions and 2 deletions
20
flask/app.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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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…
Add table
Add a link
Reference in a new issue