[Web-SIG] WSGI start_response exc_info argument
Phillip J. Eby
pje at telecommunity.com
Wed Mar 30 21:22:15 CEST 2005
At 10:57 AM 3/30/05 -0600, Ian Bicking wrote:
>Phillip J. Eby wrote:
>>That's not the use case. The parameter exists so error handling code
>>doesn't have to care whether start_response has already been called.
>>Thus, applications and middleware can be simpler because they don't need
>>to track that bit of state information that the server is already
>>tracking. So, calling start_response when it has already been called
>>causes the error handler to abort and fall back to the next higher error
>>handler, all the way up to the "real" server. IOW, it's a way of
>>guaranteeing immediate request termination if an error occurs once the
>>response has begun.
>>Of course, any logging or notification error handlers in the stack will
>>receive the error in the normal way; it's just that if they also try to
>>start a response, they'll be aborted and the error will bubble up to the
>>next handler. Does that make more sense now?
>
>I guess, but it seems to complicate most middleware for the benefit of a
>small number of middlewares. My current middleware (all of which is
>written ignorant of this argument) does something like:
>
>class ErrorMiddleware(object):
>
> def __init__(self, application, show_exceptions=True,
> email_exceptions_to=[], smtp_server='localhost'):
> self.application = application
> self.show_exceptions = show_exceptions
> self.email_exceptions_to = email_exceptions_to
> self.smtp_server = smtp_server
>
> def __call__(self, environ, start_response):
> # We want to be careful about not sending headers twice,
> # and the content type that the app has committed to (if there
> # is an exception in the iterator body of the response)
> started = []
>
> def detect_start_response(status, headers):
> started.append(True)
> return start_response(status, headers)
>
> try:
> app_iter = self.application(environ, detect_start_response)
> return self.catching_iter(app_iter, environ)
> except:
> if not started:
> start_response('500 Internal Server Error',
> [('content-type', 'text/html')])
> # @@: it would be nice to deal with bad content types here
> dummy_file = StringIO()
> response = self.exception_handler(sys.exc_info(), environ)
> return [response]
>
>
>It really should capture the headers, and maybe buffer them itself (in
>which case it would also have to intercept the writer), so that it can
>deal more gracefully with a case where content type is set or
>something. But all that annoying stuff is better kept to this one piece
>of middleware, instead of making everything more difficult with that extra
>argument to start_response.
Um, the argument is *precisely* there so you don't need all of that! Try this:
def __call__(self, environ, start_response):
try:
app_iter = self.application(environ, detect_start_response)
except:
start_response('500 Internal Server Error',
[('content-type', 'text/html')],
sys.exc_info())
# etc...
No need to track the startedness here, because the upstream start_response
will reraise the error if you've already started, thus breaking out of the
middleware and getting back to the calling server.
More information about the Web-SIG
mailing list