In the last couple of (or even more :) days Yuval and I have been endlessly discussing what the asynchronous response API would look like in PSGI applications. And that was also becoming one of the most frequently asked questions on my side, since many people from AnyEvent, POE and perlbal (Danga::Socket) land are curious how to port their non-blocking application to let them run on PSGI servers.
Thursday while having frodwith on board in the discussion (that was really helpful since he has a third person view as a POE developer) and we sorted out the nicely done middle ground, which is actually the revive of our original start_response callback we abandoned in favor of IO::Writer-like abstraction.
So, basically the idea is the same as the original Python WSGI's start_response but this callback is NOT an optional parameter to the app because that stands in the way of everybody in the chain including middleware and that sucks. Instead, an app can optionally return a callback that accepts another callback to which you can return the response array ref (code, headers and body) if you want to delay your response.
my $app = sub { my $env = shift; return sub { my $respond = shift; # do some event stuff $event->callback(sub { $respod->([ $code, $headers, $body ]) }); }; };
If you also want to delay the content body delivery as well (i.e. streaming) you can omit the body, in which case you'll get the writer object that has write(), close() and poll_cb().
my $app = sub { my $env = shift; return sub { my $respond = shift; my $w = $respond->([ 200, [ 'Content-Type' => 'text/plain' ]]); # no $body here # do more event stuff $event->callback(sub { $w->write($body) }); $event->done_callback(sub { $w->close }); }; };
I plan to update the PSGI specification to address this optional response style in a few days. Yuval also has a nicely summarized blog post on this, and we already added this callback style to our AnyEvent, Danga::Socket, Coro and POE backend as well as most middlewares, including automatic chunking middleware (supposedly used by servers, not apps) which I line-by-line cloned from Rack :)
Meanwhile Yuval is working on his data-pimping REST machine Hydrant and I've been working on Real-Time-optimized web framework Tatsumaki, and both will be built on top of PSGI/Plack from the ground up. This will definitely be how web frameworks in Perl would look like in 2010.
Friday night I was at my friend's party and five people talked to me how awesome Plack is and it's changing the world. (Disclosure: it was a party full of my Six Apart co-workers and alumni like Artur, Randy and Simon, so that's not that surprising :))
Unless I've missed something, it seems like there isn't any way for the server to determine whether a particular app is going to operate in blocking or non-blocking mode ahead of time.
While I guess in most cases this isn't that big of a deal, I think it would be nice if the server could detect this and transparently run the app via coro or a child process so that blocking apps and middleware can be run in non-blocking servers while also allowing the non-blocking ones to run in-process.
I guess also, conversely, a non-event-based server might want to do the opposite and fake up an event environment for a non-blocking app, though at least in this direction the server can wait and see what the app returns and switch if the return value is a CODE ref.
Posted by: Martin Atkins | 2009.10.17 at 21:02
> it seems like there isn't any way for the server to determine whether a particular app is going to operate in blocking or non-blocking mode ahead of time.
Right, but I don't think it's a big deal: it's app developers or end user's responsibility to run the blocking application on multi-process server. At least apps can warn (or even die) when they know their apps block and 'psgi.multiprocess' is false and 'psgi.nonblocking' is true, etc.
> I guess also, conversely, a non-event-based server might want to do the opposite and fake up an event environment for a non-blocking app, though at least in this direction the server can wait and see what the app returns and switch if the return value is a CODE ref.
I guess that's what our Middleware::Writer is trying to achieve, but this doesn't work if your application is truely non-blocking and the response doesn't immediately start. Again, I don't think it's a huge deal, though :)
Posted by: miyagawa | 2009.10.17 at 21:07
This looks really familiar, it's a very similar solution to the one I used in Thin over in ruby land.
The idea of dropping the body from the tuple entirely is definitely interesting for this use case, although that only covers the streaming form of async.
Posted by: raggi | 2010.02.03 at 03:08
Well, actually this writer interface with dropping the body from the tuple is now implemented in the synchronous backend such as CGI or mod_perl as well, so it doesn't really matter if it's async or not.
Posted by: miyagawa | 2010.02.03 at 09:59
Though since Ruby has cheap threads and even Fiber, you can do the same thing by implementing #each in the body without this streaming interface, i think. We can do the same using Coro (like Fiber/NeverBlock in Ruby) using our Coro server but it's not standard here.
Posted by: miyagawa | 2010.02.03 at 10:24