Filter to modify json response

I’m writing a Filter to parse the JSON response that a 3rd party site returns and modify the value of one of the JSON fields.

I’ve followed the Filter example in https://www.arpalert.org/src/haproxy-lua-api/3.0/index.html#filter-class but I’m stuck at how can I send the modified response down to the client.

In the http_payload callback I’m able to modify the desired field, that’s not the issue.

The issue is how can I update the headers so that the ‘content-length’ header reflects the new length, and once updated how can I send the modified response down to the client.

Tried to update the headers inside the http_payload callback but it errors out whenever I use any of the header functions.

Tried to update the headers inside the http_headers callback but at that point in time, the new content-length is not yet known.

Tried updating the headers inside the http_end but also got errors.

My thought was to keep the headers and body as members of the filter class so I could access them regardless of callback function, but then I’m not sure how can I send the modified body and headers down to the client.

The closes I got was to use http_msg:send in the http_payload callback which was able to send the json body modified in http_payload callback along with the headers I modified in http_headers callback, but unfortunatey without an updated content-lenght value.

Searching for filter examples in Google or even in Github was a fruitless exercise so I’m currently stuck.

Here’s my code:

-- Import the necessary Lua JSON library
local json = require("cjson")

-- Define a Lua filter class
ModifyResponse = {}
ModifyResponse.id = "Modify Response Filter"
ModifyResponse.flags = filter.FLT_CFG_FL_HTX
ModifyResponse.__index = ModifyResponse


function ModifyResponse:new()
    local flt = {}
    setmetatable(flt, ModifyResponse)
    flt.req_len = 0
    flt.res_len = 0
    flt.message = "Lua was here!"
    flt.headers = {}
    flt.body = ""
    return flt
end

-- --------------------------------------
-- Callback functions for the Lua filter
-- --------------------------------------
function ModifyResponse:start_analyze(txn, chn)
    filter.register_data_filter(self, chn) -- mandatory to call http_payload()
end

function ModifyResponse:end_analyze(txn, chn)
end

function ModifyResponse:http_headers(txn, http_msg)
    if http_msg.channel:is_resp() then
        local content_type = http_msg:get_headers()["content-type"][0]
        if content_type and content_type:match("application/json") then
            -- Since we will be modifying the response body, we need to remove the Content-Length header so it is recalculated
            http_msg:del_header("content-length")
            -- We can add a custom header to indicate that the response was manipulated by us
            http_msg:add_header("x-haproxy-filter", "modify_response_filter")
            -- Keep things in the class just in case we can use that elsewhere
            self.headers = http_msg:get_headers()

            return filter.CONTINUE
        else
            core.log(core.err, "Unexpected upstream response content-type " .. content_type)
            return filter.ERROR
        end
    end
end

function ModifyResponse:http_payload(txn, http_msg)
    if http_msg.channel:is_resp() then
        core.Info(string.format("resp body --> %s", http_msg:body()))

		-- Get the json responese body, lookup the desired key and update its value.
        local response_body = json.decode(http_msg:body())
        if response_body["result"]["orderStatus"] then
            for _, order in ipairs(response_body["result"]["orderStatus"]) do
                if order["status"] == "Completed" then
                    order["status"] = self.message
                end
            end

            local modified_body = json.encode(response_body)
            core.Info(string.format("modified resp body --> %s", modified_body))
			
			-- Calculate the new content-length
            self.res_len = self.res_len + modified_body:len()

			-- Update class members
            self.body = modified_body
            self.headers["content-length"] = self.res_len

			-- I'm not sure if this is the place where we should do this. It kind of works but won't include the updated content-length header
            http_msg:remove()
            http_msg:send(modified_body)
        end
	end
end

function ModifyResponse:http_end(txn, http_msg)
    if http_msg.channel:is_resp() then
	
        core.Info(string.format("Response length: %d", self.res_len))
        core.Info("Response body: " .. self.body)
        core.Info("Response headers: " .. json.encode(self.headers))
		
		-- Tried setting the new content-length header here but any header related funtions throw exceptions when used in this callback
		-- Tried sending the modififed body here but any body related funtions throw exceptions when used in this callback
        -- Not sure what this callback can be used for then!
		
    end
end

---------------------------------------------------
-- Register the Lua filter
----------------------------------------------------
core.register_filter("modify_response_filter", ModifyResponse, function(flt , args)
    return flt
end)