Making a separate HTTP Request from Lua ("http-req" action)

Has any of you guys been able to successfully do a separate HTTP-Request from a HAProxy Lua “http-req” action handler?

Overview:
In my setup I have a HAProxy with the frontends facing the internet and the backends pointing to HTTP/Rest-services on internal servers.

I’m trying to create a HTTP header rewrite mechanism in HAProxy (with Lua) that automatically translates opaque identification tokens (similar to random session strings), coming as headers from the internet requests, to JWT-tokens as Authorization: Bearer

Rewriting a header with a Lua action is easy enough following the docs:
$ cat /etc/haproxy/haproxy_test.lua

counter = 0

core.register_action("hello_world", {"http-req"}, function(txn)
        txn:Info("Hello world")
        txn.http:req_del_header("MH-Header")
        counter = counter + 1
        txn.http:req_add_header("MH-Header", "1337" .. counter)
end)

What I want to do with my mechanism is:

  1. Get the value of the opaque identification token OIT from an incoming HTTP header.
  2. Make a completely separate HTTP request from a HAProxy Lua script to an Identification Service that can translate the OIT to a JWT-Token
  3. Use the resulting JWT-Token (from #2) and insert it as a header into the request making it available to the target service(s) in the backend.

My problem is that I cannot get the HTTP-request to work from the HAProxy Lua environment.

$ cat /etc/haproxy/haproxy_test2.lua


http = require("socket.http")
counter = 0

core.register_action("hello_world", {"http-req"}, function(txn)

        txn:Info("Hello world1")
        local resp = {}
        local r, c, h, s = http.request{
                url = "http://localhost:8002/index.html",
                sink = ltn12.sink.table(resp)
                --create = core.tcp
        }

        txn:Info("Hello world2")
        txn.http:req_del_header("MH-Header")
        counter = counter + 1
        txn:Info("Hello world3")
        txn.http:req_add_header("MH-Header", "1337 " .. counter .. r)
        txn:Info("Hello world4")
end)

This simply does not work.

I’m no experienced Lua programmer but i managed to get a similar Lua program to work from the command line, outside the HAProxy environment, like this (uses https://github.com/kikito/inspect.lua):

$ cat test3.lua

inspect = require('inspect')
http = require("socket.http")

function a()
        local resp = {}
        local r, c, h, s = http.request{
                url = "http://localhost:8002/",
                sink = ltn12.sink.table(resp)
        }
        print(r)
        print(c)
        print(inspect(h))
        print(s)
        print(inspect(resp))
end

a()

I guess this might have to do with the non-blocking sockets and Lua environment when running inside HAProxy, but I’m currently stuck.

I’m bumping this thread because I’m exactly in the same boat as you. This is now quite a while ago, but does anyone have any experience with this? Specifically, sending requests from within HAProxy lua scripts? Similarly to OP I’ve produced a script that locally can send requests, but launching it via haproxy doesn’t seem to produce any traffic…

So to expand a bit, after using https.request() rather than http.request(), I can send HTTP traffic once the script is launched, but only upon launch - so I suspect that this request occurs during the lua-load … portion of the config.

I want to send a request every time a certain condition is met: specifically, every time a new string is passed to the lua function as compared to the previous string passed. (I want to send an alert once the backend for a given frontend changes, as an indication of a health check failure).

However, the request is never sent after the initial lua-load. The condition on which it should trigger does evaluate, as the print() message in that portion is visible in logs.

package.path = package.path .. ';local/share/lua/5.1/?.lua'
local http = require("socket.http")
local https = require("ssl.https")
local ltn12 = require"ltn12"
local json = require"json"
previous_backend = " "

local respbody = {} -- for the response body

url = "https://hooks.slack.com/services/TF4AS...." -- slack webhook target

local function monitor_backend(current_backend)
        local msg = '"Switched forwarding requests from: ' .. previous_backend .. 'to: ' .. current_backend ..  '"'
        local reqbody = '{"text": ' .. msg .. '}'
    if previous_backend == current_backend then
        return
    else
    print(msg) -- this does get triggered as seen in logs, so the condition evaluates well enough
    local result, respcode, respheaders, respstatus = https.request {
        method = "POST",
        url = url,
        source = ltn12.source.string(reqbody),
        headers = {
            ["content-type"] = "text/plain",
            ["content-length"] = tostring(#reqbody)
        },
        sink = ltn12.sink.table(respbody)
    }
        previous_backend = current_backend
    end
return
end
monitor("test") -- running this will successfully send a request to my slack webhook upon launching of the script 
core.register_converters("monitor_backend", monitor_backend)

And this is the frontend:

frontend ingress
        mode http
        bind :10080
        acl is_backend1_dead nbsrv(backend) lt 1
        acl is_backend2_dead nbsrv(backend2) lt 1
        default_backend backend1
        use_backend backend2 if is_backend1_dead !is_backend2_dead
        http-request set-var(txn.current_backend) str("backend1 "),lua.monitor_backend if !is_backend1_dead
        http-request set-var(txn.current_backend) str("backend2"),lua.monitor_backend if is_backend1_dead !is_backend2_dead

To anyone interested, while debugging I noticed that the error was to do with resolving the domain, and I subsequently found the answer here: how to send https request from lua in haproxy before routing request? - Stack Overflow