[Web-SIG] Server-side async API implementation sketches
P.J. Eby
pje at telecommunity.com
Sun Jan 9 18:03:38 CET 2011
At 06:06 AM 1/9/2011 +0200, Alex Grönholm wrote:
>A new feature here is that the application itself yields a (status,
>headers) tuple and then chunks of the body (or futures).
Hm. I'm not sure if I like that. The typical app developer really
shouldn't be yielding multiple body strings in the first place. I
much prefer that the canonical example of a WSGI app just return a
list with a single bytestring -- preferably in a single statement for
the entire return operation, whether it's a yield or a return.
IOW, I want it to look like the normal way to do thing is to just
return the whole request at once, and use the additional difficulty
of creating a second iterator to discourage people writing iterated
bodies when they should just write everything to a BytesIO and be done with it.
Also, it makes middleware simpler: the last line can just yield the
result of calling the app, or a modified version, i.e.:
yield app(environ)
or:
s, h, b = app(environ)
# ... modify or replace s, h, b
yield s, h, b
In your approach, the above samples have to be rewritten as:
return app(environ)
or:
result = app(environ)
s, h = yield result
# ... modify or replace s, h
yield s, h
for data in result:
# modify b as we go
yield result
Only that last bit doesn't actually work, because you have to be able
to send future results back *into* the result. Try actually making
some code that runs on this protocol and yields to futures during the
body iteration.
Really, this modified protocol can't work with a full async API the
way my coroutine-based version does, AND the middleware is much more
complicated. In my version, your do-nothing middleware looks like this:
class NullMiddleware(object):
def __init__(self, app):
self.app = app
def __call__(environ):
# ACTION: pre-application environ mangling
s, h, body = yield self.app(environ)
# modify or replace s, h, body here
yield s, h, body
If you want to actually process the body in some way, it looks like:
class NullMiddleware(object):
def __init__(self, app):
self.app = app
def __call__(environ):
# ACTION: pre-application environ mangling
s, h, body = yield self.app(environ)
# modify or replace s, h, body here
yield s, h, self.process(body)
def process(self, body_iter):
while True:
chunk = yield body_iter
if chunk is None:
break
# process/modify chunk here
yield chunk
And that's still a lot simpler than your sketch.
Personally, I would write both of the above as:
def null_middleware(app):
def wrapped(environ):
# ACTION: pre-application environ mangling
s, h, body = yield app(environ)
# modify or replace s, h, body here
yield s, h, process(body)
def process(body_iter):
while True:
chunk = yield body_iter
if chunk is None:
break
# process/modify chunk here
yield chunk
return wrapped
But that's just personal taste. Even as a class, it's much easier to
write. The above middleware pattern works with the sketches I gave
on the PEAK wiki, and I've now updated the wiki to include an example
app and middleware for clarity.
Really, the only hole in this approach is dealing with applications
that block. The elephant in the room here is that while it's easy to
write these example applications so they don't block, in practice
people read files and do database queries and whatnot in their
requests, and those APIs are generally synchronous. So, unless they
somehow fold their entire application into a future, it doesn't work.
>I liked the idea of having a separate async_read() method in
>wsgi.input, which would set the underlying socket in nonblocking
>mode and return a future. The event loop would watch the socket and
>read data into a buffer and trigger the callback when the given
>amount of data has been read. Conversely, .read() would set the
>socket in blocking mode. What kinds of problems would this cause?
That you could never *call* the .read() method outside of a future,
or else you would block the server, thereby obliterating the point of
having the async API in the first place.
More information about the Web-SIG
mailing list