Hello!
On Sun, Dec 23, 2012 at 4:04 AM, James Hurst wrote:
>
> Of course, generally I agree - cosockets are fantastic and make lots of
> sense. But subrequests are also very important in reality, because they
> allow you to reuse other parts of your configuration. I totally agree that
> if you're using subrequests just to do some arbitrary i/o, then cosockets
> should be explored, and the more client drivers we have in the ecosystem the
> better!
>
Yes, people have already been adding their new lua-resty-* libraries.
And I'm going to implement more client drivers myself too :)
> But things like pools of upstream backends, for example, come for free with
> the subrequest model. Why implement a HTTP Lua client for proxying to
> upstream servers when the proxy module + upstream gets you everything you
> need, tested and guaranteed? Often this separation of concerns is really
> desirable too. For example, my proxy cache module shouldn't need to care
> about the origin itself - it's really elegant to use subrequests because
> Nginx configuration abstracts the details.
>
I can understand this :)
> I'm interested to learn what it might take to do non-buffered processing
> with subrequests. Is it a large refactor do you think? We may be able to
> throw some resource at it given some guidance on design.
>
No, it will not be a large refactor. We just need to implement a new
set of ngx.subreq.* API. We cannot reuse the ngx.location.capture*
API.
Here is the basic idea for the API (details do not matter and are
subject to change):
-- ngx.subreq.spawn will not return until the response headers
-- are received or an error occurs
local sr, err = ngx.subreq.spawn("/proxy", ...)
if not sr then
ngx.log(ngx.ERR, "failed to spawn subrequest: ", err)
ngx.exit(500)
end
-- the sr.get_headers call does not do I/O itself,
-- it just retrieves the response headers received
-- in ngx.subreq.spawn():
local resp_headers, err = sr.get_headers()
-- now let's receive the response body chunk by chunk:
while true do
local chunk, err = sr.receive_body_chunk(4096)
if err == "eof" then
-- process the last chunk
break
end
if err then
ngx.log(ngx.ERR, "failed to receive body chunk: ", err)
ngx.exit(500)
end
-- process the data chunk
end
Yes, it looks very much like the TCP cosocket API.
Regarding implementation details, here's my proposal:
* return NGX_AGAIN and avoid marking the incoming chain bufs as
"consumed" in our ngx_lua "capture" output body filter. Upstream
modules like ngx_proxy that supports non-buffered output mode can work
out of the box.
* track each subrequest object's state in each coroutine, just as we
do for the cosocket objects.
Essentially the basic idea is to emulate a "slow client" in ngx_lua's
"capture" output filter such that the content handler defined in the
location serving the subrequest can have a chance to slow down while
outputing response body data. This should work if the corresponding
content handler does support the non-buffered output mode.
> Also, any other suggestions on practical ways to "protect" Lua from
> accidental large subrequests? Because in reality, right now I'd be happy to
> simply handle large responses as an exception and redirect to them directly.
> I just can't see a nice way to do this without first proxying at least the
> size of the largest response we'll accept, before bailing and redirecting.
> Not great really.
>
We can allow the user to set an upper limit on the size of the
subrequest captured by ngx.location.capture*. For example:
local res = ngx.location.capture("/proxy", { max_body_size = 4096 })
if res.truncated then
-- do your error handling here
end
Do you like it?
You're very welcome to contribute patches for these new features :)
Best regards,
-agentzh