Clarifying HTTP exception behavior wrt. 500 responses.
See original GitHub issueRelated: #54
Having worked through the implementation implications in both uvicorn and starlette, I’m now confident that the behaviors should be:
- Servers should log the exception. The must check if a response has been started, and must send a 500 if no response has been sent. They should not issue any particular warning if an exception is raised once a response has already been sent, as this represents normal behavior for a well-behaved application, although they may issue an explicit warning in the case of an exception where the response has started but not completed.
- Applications may choose to catch exceptions, and send an appropriate 500 page (eg. debug traceback or user-facing 500 error page) if the response has not yet started. If they do so then they must subsequently still raise the exception. This behavior enables the server to continue to log the traceback as desired, and makes the exception visible to any error-monitoring middleware.
Both Uvicorn’s WSGI middleware and asgiref’s WsgiToAsgi
appear to have incorrect behavior here, in that they raise exc_info
immediately in both cases, rather than sending an application’s 500 response that is returned alongside with the exc_info
and then raising the exception. (End result - the server gets to log the exception and returns a 500 response, but we don’t get any application-defined 500 responses, either traceback or user-facing 500s)
An example of what I’ve used to test server/application interaction for resolving this wrt uvicorn’s middleware… Taken from the WSGI spec
import uvicorn
import sys
def app(environ, start_response):
try:
# regular application code here
raise RuntimeError()
status = "200 Froody"
response_headers = [("content-type", "text/plain")]
start_response(status, response_headers)
return [b"normal body goes here"]
except:
# XXX should trap runtime issues like MemoryError, KeyboardInterrupt
# in a separate handler before this bare 'except:'...
status = "500 Oops"
response_headers = [("content-type", "text/plain")]
start_response(status, response_headers, sys.exc_info())
return [b"error body goes here"]
if __name__ == '__main__':
uvicorn.run(app, wsgi=True)
Here’s my resolution to the issue in uvicorn - note that exc_info
needs to be raised not at the end of start_response
(since we don’t have the response body yet) but after all of the returned byte-iterator has been sent to the send channel.
Issue Analytics
- State:
- Created 5 years ago
- Comments:8 (8 by maintainers)
OK - I won’t be able to get to either in the short term, but will leave this open to track the work.
@tomchristie Do you think there’s more work we need to do here?