-
-
Notifications
You must be signed in to change notification settings - Fork 16.7k
always pass InternalServerError instance to 500 handler #3266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Looks good to me! I think one thing worth clarifying is a case where an app registers both an @app.errorhandler(HTTPException)
def handle_http_exception(exc):
if not (400 <= exc.code <= 599):
# if it's not an actual error, use it as a response.
# this is needed e.g. for the 301 redirects that are raised
# as routing exceptions and thus end up here
return exc
elif exc.response:
# if the exception has a custom response, we always use that
# one instead of showing the default error page
return exc
return custom_http_error(...)
@app.errorhandler(Exception)
def handle_exception(exc, message=None):
return custom_unhandled_error(...)In that case I'd expect the second handler not being called for any HTTP error handled by the first one. |
18ced9a to
0982a27
Compare
jab
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the thorough explanation in the description. It seems clear this is the right approach over that taken in #2983. The code changes, tests, and especially the docs are exemplary. 💯
0982a27 to
54519ef
Compare
As of pallets/flask#3266, the original exception is passed as `exc.original_exception`
Due to multiple PRs over the last 5 years, the error handler behavior has slowly changed with the goal of being more consistent. However, after #2314 was merged in 1.0.0, these changes all cascaded together to make some inconsistent behavior finally visible.
After extensive discussion in #1281, #1291, and #1429, the goal was to make error handlers trigger for exceptions in MRO order, rather than registration order. It formalized the idea that HTTP exception classes and status codes were aliases. Registering a handler for
401was the same asUnauthorized.However, it (unintentionally?) preserved some old behavior where user errors would only be looked up against a
500error handler, not aInternalServerErrorhandler, even though the goal was for these to be aliases.#2314 ensured a more consistent lookup order between blueprints and app error handlers for codes and exception classes. #2362 simplified the code even more, and made it more correct for subclass handling. A side effect of these refactors was that it fixed the preserved behavior, so 500 and
InternalServerErrorhandlers were equivalent.All these changes had the goal of making error handler registration and triggering more intuitive, and making maintenance easier.
When an unhandled exception is raised,
handle_exceptionis triggered so that a final, generic internal server error is returned. Previously, the behavior was to pass the unhandled exception to the 500 error handler, rather than the genericInternalServerError. Now that 500 andInternalServerErrorwere the same thing and were both considered as handlers for generic error, users who registered a handler forInternalServerErroror theHTTPExceptionbase class were surprised to get other random exceptions passed to the handler, rather than strict subclasses (#2778, #2841).A fix was proposed in #2983 which continued to preserve the old behavior by making a handler for
500receive any error, while a handler forInternalServerErroronly receivedInternalServerError. I think this made the code harder to reason about, both for maintainers and for app devs.Instead, I'm going the opposite direction and ensuring that those handlers only ever receive
InternalServerErrorinstances. For unhandled errors, the exception has a neworiginal_exceptionattribute that has the original unhandled error. This will be formalized in Werkzeug 1.0.0, until thengetattrcan be used to check if the attribute is set. The upside of this is that it is safe to assume that all codes and classes are aliases, and will only receive matching classes of errors, which seems to have been the intention of previous discussions, and makes the most sense to me.The downside is that there is no way for this to be 100% backwards compatible for 500 handlers that were written assuming any exception would be passed to them, and I couldn't think of a way to have a useful deprecation warning transition.
ewill always look likeInternalServerError, possibly making existing generic error pages less useful. However, with the availability ofe.original_exception, it should be straightforward to get the intended behavior back. Code shouldn't fail in the mean time, only be less specific. I think the benefit of more consistent behavior outweighs the drawback.closes #2778
closes #2841
closes #2983
While fixing this, I noticed that
finalize_requestwas only called if a 500 error handler was found. If no custom handler was registered, then an unhandled error would skipafter_requestfunctions, saving the session, and sending therequest_finishedsignal. This is now fixed, sofinalize_requestis always called.To clear up related confusion about very generic error handlers such as
HTTPExceptionandException, more docs have been added to theerrorhandling.rstpage.handle_exceptionhas much clearer explanations of what it does too.