Re: Server-side async API implementation sketches

Alex Grönholm <alex.gronholm-S2Gd0AnZGk/[email protected]>
Newsgroups gmane.comp.python.web
Message-ID <[email protected]>
09.01.2011 19:03, P.J. Eby kirjoitti:
> 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.
Uh, so don't yield multiple body strings then? How is that so difficult?
>
>
> 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.
I fail to understand why a second iterator is necessary when we can get 
away with just one.
>
>
> 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
Asynchronous applications may not be ready to send the status line as 
the first thing coming out of the generator. Consider an app that 
receives a file. The first thing coming out of the app is a future. The 
app needs to receive the entire file until it can determine what status 
line to send. Maybe there was an I/O error writing the file, so it needs 
to send a 500 response instead of 200. This is not possible with a body 
iterator, and if we are already iterating the application generator, I 
really don't understand why the body needs to be an iterator as well.
>
>
> 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.
Did you miss the gist posted by myself (and improved by Alice)?
>
> 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.
>
Outside of the application/middleware you mean? I hope there isn't any 
more confusion left about what a future is. The fact is that you cannot 
use synchronous API calls directly from an async app no matter what. 
Some workaround is always necessary.


_______________________________________________
Web-SIG mailing list
[email protected]
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.