[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