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)