-
-
Notifications
You must be signed in to change notification settings - Fork 954
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rejig ExceptionMiddleware and ServerErrorMiddleware (#193)
* Rejig ExceptionMiddleware and ServerErrorMiddleware * Tweak DebugMiddleware implementation * Support custom 500 handlers * Exception handling updates
- Loading branch information
1 parent
0c34538
commit 9f3dcb7
Showing
11 changed files
with
297 additions
and
280 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,93 +1,76 @@ | ||
|
||
Starlette includes an exception handling middleware that you can use in order | ||
to dispatch different classes of exceptions to different handlers. | ||
|
||
To see how this works, we'll start by with this small ASGI application: | ||
Starlette allows you to install custom exception handlers to deal with | ||
how you return responses when errors or handled exceptions occur. | ||
|
||
```python | ||
from starlette.exceptions import ExceptionMiddleware, HTTPException | ||
|
||
|
||
class App: | ||
def __init__(self, scope): | ||
raise HTTPException(status_code=403) | ||
|
||
|
||
app = ExceptionMiddleware(App) | ||
``` | ||
|
||
If you run the app and make an HTTP request to it, you'll get a plain text | ||
response with a "403 Permission Denied" response. This is the behaviour that the | ||
default handler responds with when an `HTTPException` class or subclass is raised. | ||
|
||
Let's change the exception handling, so that we get JSON error responses | ||
instead: | ||
from starlette.applications import Starlette | ||
from starlette.responses import HTMLResponse | ||
|
||
|
||
```python | ||
from starlette.exceptions import ExceptionMiddleware, HTTPException | ||
from starlette.responses import JSONResponse | ||
|
||
HTML_404_PAGE = ... | ||
HTML_500_PAGE = ... | ||
|
||
class App: | ||
def __init__(self, scope): | ||
raise HTTPException(status_code=403) | ||
|
||
app = Starlette() | ||
|
||
def handler(request, exc): | ||
return JSONResponse({"detail": exc.detail}, status_code=exc.status_code) | ||
|
||
@app.exception_handler(404) | ||
async def not_found(request, exc): | ||
return HTMLResponse(content=HTML_404_PAGE) | ||
|
||
app = ExceptionMiddleware(App) | ||
app.add_exception_handler(HTTPException, handler) | ||
@app.exception_handler(500) | ||
async def server_error(request, exc): | ||
return HTMLResponse(content=HTML_500_PAGE) | ||
``` | ||
|
||
Now if we make a request to the application, we'll get back a JSON encoded | ||
HTTP response. | ||
|
||
By default two types of exceptions are caught and dealt with: | ||
|
||
* `HTTPException` - Used to raise standard HTTP error codes. | ||
* `Exception` - Used as a catch-all handler to deal with any `500 Internal | ||
Server Error` responses. The `Exception` case also wraps any other exception | ||
handling. | ||
If `debug` is enabled and an error occurs, then instead of using the installed | ||
500 handler, Starlette will respond with a traceback response. | ||
|
||
The catch-all `Exception` case is used to return simple `500 Internal Server Error` | ||
responses. During development you might want to switch the behaviour so that | ||
it displays an error traceback in the browser: | ||
|
||
``` | ||
app = ExceptionMiddleware(App, debug=True) | ||
```python | ||
app = Starlette(debug=True) | ||
``` | ||
|
||
This uses the same error tracebacks as the more minimal [`DebugMiddleware`](../debugging). | ||
As well as registering handlers for specific status codes, you can also | ||
register handlers for classes of exceptions. | ||
|
||
The exception handler currently only catches and deals with exceptions within | ||
HTTP requests. Any websocket exceptions will simply be raised to the server | ||
and result in an error log. | ||
In particular you might want to override how the built-in `HTTPException` class | ||
is handled. For example, to use JSON style responses: | ||
|
||
## ExceptionMiddleware | ||
```python | ||
@app.exception_handler(HTTPException) | ||
async def http_exception(request, exc): | ||
return JSONResponse({"detail": exc.detail}, status_code=exc.status_code) | ||
``` | ||
|
||
The exception middleware catches and handles the exceptions, returning | ||
appropriate HTTP responses. | ||
## Errors and handled exceptions | ||
|
||
* `ExceptionMiddleware(app, debug=False)` - Instantiate the exception handler, | ||
wrapping up it around an inner ASGI application. | ||
It is important to differentiate between handled exceptions and errors. | ||
|
||
Adding handlers: | ||
Handled exceptions do not represent error cases. They are coerced into appropriate | ||
HTTP responses, which are then sent through the standard middleware stack. By default | ||
the `HTTPException` class is used to manage any handled exceptions. | ||
|
||
* `.add_exception_handler(exc_class, handler)` - Set a handler function to run | ||
for the given exception class. | ||
Errors are any other exception that occurs within the application. These cases | ||
should bubble through the entire middleware stack as exceptions. Any error | ||
logging middleware should ensure that it re-raises the exception all the | ||
way up to the server. | ||
|
||
Enabling debug mode: | ||
In order to deal with this behaviour correctly, the middleware stack of a | ||
`Starlette` application is configured like this: | ||
|
||
* `.debug` - If set to `True`, then the catch-all handler for `Exception` will | ||
not be used, and error tracebacks will be sent as responses instead. | ||
* `ServerErrorMiddleware` - Returns 500 responses when server errors occur. | ||
* Installed middleware | ||
* `ExceptionMiddleware` - Deals with handled exceptions, and returns responses. | ||
* Router | ||
* Endpoints | ||
|
||
## HTTPException | ||
|
||
The `HTTPException` class provides a base class that you can use for any | ||
standard HTTP error conditions. The `ExceptionMiddleware` implementation | ||
defaults to returning plain-text HTTP responses for any `HTTPException`. | ||
handled exceptions. The `ExceptionMiddleware` implementation defaults to | ||
returning plain-text HTTP responses for any `HTTPException`. | ||
|
||
* `HTTPException(status_code, detail=None)` | ||
|
||
You should only raise `HTTPException` inside routing or endpoints. Middleware | ||
classes should instead just return appropriate responses directly. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.