haproxytech / haproxy-lua-http

Simple Lua HTTP helper && client for use with HAProxy.
Apache License 2.0
56 stars 23 forks source link

request.create() function is not instanciating a complete new request object #13

Closed stncrn closed 3 years ago

stncrn commented 3 years ago

Hello,

I have found a small issue with the request.create() function.

Let's start with an example:

-- the servers that lua http client will call
servers = {}
table.insert(servers, { name = 'server42', baseUrl = 'http://dummy42' } )
table.insert(servers, { name = 'server44', baseUrl = 'http://dummy44' } )

function replaceBaseUrl(url, newBaseUrl)
    return url:gsub("^(https?://.-)/", newBaseUrl .. '/')
end

function routeBroadcast(applet)
    -- get the incoming request
    local originalRequest = http.request.parse(applet)

    -- now create and send modified requests to all servers
    for k, server in pairs(servers) do
        -- copy the orignal request
        local newRequest = http.request.create(originalRequest)
        -- adjust url
        newRequest.url = replaceBaseUrl(originalRequest.url, server.baseUrl)
        newRequest.headers['host'] = nil

        -- do some headers customization for each server
        -- in reality, much more complex than this
        if server.name == 'server42' then
            newRequest.headers['my-custom-header'] = nil
        end

        http.send(originalRequest.method, newRequest)
    end

    -- write final answer to hap client
    local body = 'all done'
    applet:add_header("Content-Length", #tostring(body))
    applet:set_status(200)
    applet:start_response()
    applet:send(tostring(body))
end

core.register_service("broadcast", "http", function(applet)
    return routeBroadcast(applet)
end)

Basically, just a broadcasting backend, sending an incoming request to several servers. If the incoming request contains a 'my-custom-header' header, the customization in my loop will remove it for the call to the 1st backend. But the header should still be there for the call to the 2nd server.

Seems that the faulty lines are these ones in the http module:

    self.url = t.url or nil
    self.headers = t.headers or {}
    self.data = t.data or nil
    self.params = t.params or {}
    self.auth = t.auth or {}

url and data are properly copied, as they're strings. But for the 3 tables (headers, params and auth), only the reference of the first request is copied.

Each further edit is therefore targeting the original request, as well as its copy.

Using a proper object copy function should do the trick: Haven't tried it myself for the moment : http://lua-users.org/wiki/CopyTable

anezirovic commented 3 years ago

Thanks for catching this, you are totally right. We need deep copy for headers at least.

Please try the latest master, it should solve the issue.

stncrn commented 3 years ago

Great! Thanks for the fix. I sadly am quite busy at the moment, and won't be able to try soon. (I used a quick and dirty workaround for the only header I was manipulating, as I was quite in a hurry)
But yes, the commit seems ok.

anezirovic commented 3 years ago

No problem, I'll close the issue, feel free to reopen again.