daurnimator / lua-http

HTTP Library for Lua. Supports HTTP(S) 1.0, 1.1 and 2.0; client and server.
https://daurnimator.github.io/lua-http/
MIT License
778 stars 80 forks source link

Infinite loop when onstream func throws error before stream is read #204

Open leafo opened 1 year ago

leafo commented 1 year ago

The following code will loop infinitely when you make a request to the server. onstream and onerror are called endlessly, the requesting client hangs. There doesn't appear to be a way to finalize the stream in the error handler based on what I've looked at so far.

I would expect it to terminate the request with a 503 error, as if you had triggered an error after getting headers.

My guess that is that since the stream doesn't make any progress being processed in the stream handler before error it's re-queued to be processed again, hence the infinite loop.

Note: It's necessary to provide a custom onerror function, as the default one will terminate the entire server by throwing an error.

local http_server = require "http.server"
local http_headers = require "http.headers"

local function onstream(server, stream)
    error("throwing an error before get_headers will cause infinite loop!")

    local req_headers = stream:get_headers()
    error("this is fine") -- error after get_headers will terminate request with 503 error as expected
end

local server = assert(http_server.listen {
    host = "127.0.0.1";
    port = 8080;
    onstream = onstream;
    onerror = function(myserver, context, op, err, errno)
        local msg = op .. " on " .. tostring(context) .. " failed"
        if err then
            msg = msg .. ": " .. tostring(err)
        end
        assert(io.stderr:write(msg, "\n"))
    end;
})

assert(server:loop())
daurnimator commented 1 year ago

onstream fires when there is a request ready to be handled: if not even a single byte of it is read, then onstream will fire again on the next main loop iteration. If you haven't called get_headers, then it hasn't even started handling the request, and as such it can't terminate the request.