[Web-SIG] Sketching a WSGI 2-to-1 adapter with greenlets

P.J. Eby pje at telecommunity.com
Sat Sep 19 00:07:57 CEST 2009


On his blog, Graham mentioned some skepticism about skipping WSGI 1.1 
and going straight to 2.0, due to concern that people using write() 
would need to make major code changes to go to WSGI 2.0.

Now, if we ignore the part of the spec that says "New WSGI 
applications and frameworks *should not* use the write() callable if 
it is possible to avoid doing so," there does need to be some 
reasonable way for those people to make their apps work with the 
newer spec.  On CPython at least, this can be implemented using 
greenlets, and on other Python implementations it could be done with 
threads.  Here's a quick and dirty, untested sketch (no error 
checking, no version handling) of how it could be done with greenlets:

def two_to_one(app):
     def wrapper(environ):
         buffer = []
         header = []

         def start_response(status, headers):
             header.append(status)
             header.append(headers)
             return write

         def write(data):
             buffer.append(data)
             greenlet.getcurrent().parent.switch(None)

         child = greenlet(app)
         response = child.switch(environ, start_response)

         if not header:
             # XXX start_response wasn't called, error!

         if not buffer:
             # write wasn't called, so just pass it through
             return header[0], header[1], response

         def yield_all():
             response = None
             try:
                 while buffer:
                     yield buffer.pop(0)
                     response = child.switch()
                 # XXX check for response being non-empty
             finally:
                 if hasattr(response, 'close'):
                     response.close()

         return header[0], header[1], yield_all()

     return wrapper

As you can see, I've stuck in some XXX comments for where there 
should be more error checking or handling, and there would probably 
be some other additions as well.  However, this adapter handles both 
write()-using and non-write()-using WSGI 1 apps, and converts them to 
the WSGI 2 calling convention, by making the write() function call 
perform a non-local return from the application.

Doing this with threads would be similar, but there are more design 
decisions to make, i.e., will you use a single worker thread that you 
send requests to, or just start a new thread for each request?  In 
either case, the start_response() and write() in that thread would 
simply write data to a Queue.Queue that's read by the adapter.  The 
code running in the other thread would handle closing the app's 
response (if need be), after piping all the app's output to the 
queue.  You'd also need to decide if you're going to support 
interrupting the application (e.g. by returning an error from 
write(), or by calling throw() on a generator) if the wrapper is 
closed before its time.

(Of course, none of these shenanigans are necessary for well-behaved 
apps and frameworks that don't use write(); the above adapter would 
lose its yield_all function and all the greenlet usage, substituting 
some error raise code for the body of write() in that case.)



More information about the Web-SIG mailing list