Merge pull request #4492 from pallets/debug-messages
update some debug message behavior
This commit is contained in:
commit
ce7b884b73
4 changed files with 62 additions and 47 deletions
|
|
@ -1457,17 +1457,26 @@ class Flask(Scaffold):
|
||||||
)
|
)
|
||||||
|
|
||||||
def raise_routing_exception(self, request: Request) -> "te.NoReturn":
|
def raise_routing_exception(self, request: Request) -> "te.NoReturn":
|
||||||
"""Exceptions that are recording during routing are reraised with
|
"""Intercept routing exceptions and possibly do something else.
|
||||||
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.
|
|
||||||
|
|
||||||
|
In debug mode, intercept a routing redirect and replace it with
|
||||||
|
an error if the body will be discarded.
|
||||||
|
|
||||||
|
With modern Werkzeug this shouldn't occur, since it now uses a
|
||||||
|
308 status which tells the browser to resend the method and
|
||||||
|
body.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.1
|
||||||
|
Don't intercept 307 and 308 redirects.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
:internal:
|
:internal:
|
||||||
"""
|
"""
|
||||||
if (
|
if (
|
||||||
not self.debug
|
not self.debug
|
||||||
or not isinstance(request.routing_exception, RequestRedirect)
|
or not isinstance(request.routing_exception, RequestRedirect)
|
||||||
or request.method in ("GET", "HEAD", "OPTIONS")
|
or request.routing_exception.code in {307, 308}
|
||||||
|
or request.method in {"GET", "HEAD", "OPTIONS"}
|
||||||
):
|
):
|
||||||
raise request.routing_exception # type: ignore
|
raise request.routing_exception # type: ignore
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,53 +41,55 @@ class DebugFilesKeyError(KeyError, AssertionError):
|
||||||
|
|
||||||
|
|
||||||
class FormDataRoutingRedirect(AssertionError):
|
class FormDataRoutingRedirect(AssertionError):
|
||||||
"""This exception is raised by Flask in debug mode if it detects a
|
"""This exception is raised in debug mode if a routing redirect
|
||||||
redirect caused by the routing system when the request method is not
|
would cause the browser to drop the method or body. This happens
|
||||||
GET, HEAD or OPTIONS. Reasoning: form data will be dropped.
|
when method is not GET, HEAD or OPTIONS and the status code is not
|
||||||
|
307 or 308.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, request):
|
def __init__(self, request):
|
||||||
exc = request.routing_exception
|
exc = request.routing_exception
|
||||||
buf = [
|
buf = [
|
||||||
f"A request was sent to this URL ({request.url}) but a"
|
f"A request was sent to '{request.url}', but routing issued"
|
||||||
" redirect was issued automatically by the routing system"
|
f" a redirect to the canonical URL '{exc.new_url}'."
|
||||||
f" to {exc.new_url!r}."
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# In case just a slash was appended we can be extra helpful
|
if f"{request.base_url}/" == exc.new_url.partition("?")[0]:
|
||||||
if f"{request.base_url}/" == exc.new_url.split("?")[0]:
|
|
||||||
buf.append(
|
buf.append(
|
||||||
" The URL was defined with a trailing slash so Flask"
|
" The URL was defined with a trailing slash. Flask"
|
||||||
" will automatically redirect to the URL with the"
|
" will redirect to the URL with a trailing slash if it"
|
||||||
" trailing slash if it was accessed without one."
|
" was accessed without one."
|
||||||
)
|
)
|
||||||
|
|
||||||
buf.append(
|
buf.append(
|
||||||
" Make sure to directly send your"
|
" Send requests to the canonical URL, or use 307 or 308 for"
|
||||||
f" {request.method}-request to this URL since we can't make"
|
" routing redirects. Otherwise, browsers will drop form"
|
||||||
" browsers or HTTP clients redirect with form data reliably"
|
" data.\n\n"
|
||||||
" or without user interaction."
|
"This exception is only raised in debug mode."
|
||||||
)
|
)
|
||||||
buf.append("\n\nNote: this exception is only raised in debug mode")
|
super().__init__("".join(buf))
|
||||||
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
|
"""Patch ``request.files.__getitem__`` to raise a descriptive error
|
||||||
request is detected that does not use multipart form data but the files
|
about ``enctype=multipart/form-data``.
|
||||||
object is accessed.
|
|
||||||
|
:param request: The request to patch.
|
||||||
|
:meta private:
|
||||||
"""
|
"""
|
||||||
oldcls = request.files.__class__
|
oldcls = request.files.__class__
|
||||||
|
|
||||||
class newcls(oldcls):
|
class newcls(oldcls):
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
try:
|
try:
|
||||||
return oldcls.__getitem__(self, key)
|
return super().__getitem__(key)
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
if key not in request.form:
|
if key not in request.form:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
raise DebugFilesKeyError(request, key) from e
|
raise DebugFilesKeyError(request, key).with_traceback(
|
||||||
|
e.__traceback__
|
||||||
|
) from None
|
||||||
|
|
||||||
newcls.__name__ = oldcls.__name__
|
newcls.__name__ = oldcls.__name__
|
||||||
newcls.__module__ = oldcls.__module__
|
newcls.__module__ = oldcls.__module__
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ from .globals import current_app
|
||||||
from .helpers import _split_blueprint_path
|
from .helpers import _split_blueprint_path
|
||||||
|
|
||||||
if t.TYPE_CHECKING:
|
if t.TYPE_CHECKING:
|
||||||
import typing_extensions as te
|
|
||||||
from werkzeug.routing import Rule
|
from werkzeug.routing import Rule
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -110,7 +109,7 @@ class Request(RequestBase):
|
||||||
return _split_blueprint_path(name)
|
return _split_blueprint_path(name)
|
||||||
|
|
||||||
def _load_form_data(self) -> None:
|
def _load_form_data(self) -> None:
|
||||||
RequestBase._load_form_data(self)
|
super()._load_form_data()
|
||||||
|
|
||||||
# In debug mode we're replacing the files multidict with an ad-hoc
|
# In debug mode we're replacing the files multidict with an ad-hoc
|
||||||
# subclass that raises a different error for key errors.
|
# subclass that raises a different error for key errors.
|
||||||
|
|
@ -124,11 +123,14 @@ class Request(RequestBase):
|
||||||
|
|
||||||
attach_enctype_error_multidict(self)
|
attach_enctype_error_multidict(self)
|
||||||
|
|
||||||
def on_json_loading_failed(self, e: Exception) -> "te.NoReturn":
|
def on_json_loading_failed(self, e: ValueError) -> t.Any:
|
||||||
if current_app and current_app.debug:
|
try:
|
||||||
raise BadRequest(f"Failed to decode JSON object: {e}")
|
return super().on_json_loading_failed(e)
|
||||||
|
except BadRequest as e:
|
||||||
|
if current_app and current_app.debug:
|
||||||
|
raise
|
||||||
|
|
||||||
raise BadRequest()
|
raise BadRequest() from e
|
||||||
|
|
||||||
|
|
||||||
class Response(ResponseBase):
|
class Response(ResponseBase):
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ from werkzeug.exceptions import Forbidden
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
from werkzeug.http import parse_date
|
from werkzeug.http import parse_date
|
||||||
from werkzeug.routing import BuildError
|
from werkzeug.routing import BuildError
|
||||||
|
from werkzeug.routing import RequestRedirect
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
|
|
||||||
|
|
@ -1724,28 +1725,29 @@ def test_before_first_request_functions_concurrent(app, client):
|
||||||
assert app.got_first_request
|
assert app.got_first_request
|
||||||
|
|
||||||
|
|
||||||
def test_routing_redirect_debugging(app, client):
|
def test_routing_redirect_debugging(monkeypatch, app, client):
|
||||||
app.debug = True
|
|
||||||
|
|
||||||
@app.route("/foo/", methods=["GET", "POST"])
|
@app.route("/foo/", methods=["GET", "POST"])
|
||||||
def foo():
|
def foo():
|
||||||
return "success"
|
return "success"
|
||||||
|
|
||||||
with client:
|
|
||||||
with pytest.raises(AssertionError) as e:
|
|
||||||
client.post("/foo", data={})
|
|
||||||
assert "http://localhost/foo/" in str(e.value)
|
|
||||||
assert "Make sure to directly send your POST-request to this URL" in str(
|
|
||||||
e.value
|
|
||||||
)
|
|
||||||
|
|
||||||
rv = client.get("/foo", data={}, follow_redirects=True)
|
|
||||||
assert rv.data == b"success"
|
|
||||||
|
|
||||||
app.debug = False
|
app.debug = False
|
||||||
|
rv = client.post("/foo", data={}, follow_redirects=True)
|
||||||
|
assert rv.data == b"success"
|
||||||
|
|
||||||
|
app.debug = True
|
||||||
|
|
||||||
with client:
|
with client:
|
||||||
rv = client.post("/foo", data={}, follow_redirects=True)
|
rv = client.post("/foo", data={}, follow_redirects=True)
|
||||||
assert rv.data == b"success"
|
assert rv.data == b"success"
|
||||||
|
rv = client.get("/foo", data={}, follow_redirects=True)
|
||||||
|
assert rv.data == b"success"
|
||||||
|
|
||||||
|
monkeypatch.setattr(RequestRedirect, "code", 301)
|
||||||
|
|
||||||
|
with client, pytest.raises(AssertionError) as e:
|
||||||
|
client.post("/foo", data={})
|
||||||
|
|
||||||
|
assert "canonical URL 'http://localhost/foo/'" in str(e.value)
|
||||||
|
|
||||||
|
|
||||||
def test_route_decorator_custom_endpoint(app, client):
|
def test_route_decorator_custom_endpoint(app, client):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue