Getting started
Introduction to HAProxy Lua programming
HAProxy and HAProxy Enterprise include the Lua interpreter, which allows you to extend the load balancer’s functionality with custom Lua scripts. This guide is a quick introduction.
You may also find the following links helpful:
- Examples on ARP Alert
The basics Jump to heading
In the global
section of your configuration file, define the Lua files that you want to load. HAProxy and HAProxy Enterprise read these files before processing any chroot
lines in the configuration, so they can be placed outside of your chrooted directory if needed.
HAProxy and HAProxy Enterprise have a non-blocking architecture and, because of that, Lua scripts must be written in a way that avoids reading files, making network calls, or performing other actions that might block the main thread of the load balancer process during runtime. The best place to read files and perform other blocking IO calls is within a core.register_init
function. Then store that data in a global variable so that it can be accessed during runtime from a core.register_service
block.
Inside your Lua script you will add blocks that start with core.register_service
. These define functions that the load balancer will execute during runtime whenever one of the following configuration directives fires:
tcp-request content use-service lua.<name used in register>
tcp-response content use-service lua.<name used in register>
http-request use-service lua.<name used in register>
http-response use-service lua.<name used in register>
Hello world Jump to heading
First, let’s consider a basic hello world example. This will echo back (i.e. print to the screen) the the URL that the client requested.
-
Create a file named
/tmp/hello_world.lua
with the following content:hello_world.lualuacore.register_service("hello_world_tcp", "tcp", function(applet)applet:send("hello world\n")end)core.register_service("hello_world_http", "http", function(applet)local response = "The path which was requested is: '" .. applet.path .. "'\n"applet:set_status(200)applet:add_header("content-length", string.len(response))applet:add_header("content-type", "text/plain")applet:start_response()applet:send(response)end)hello_world.lualuacore.register_service("hello_world_tcp", "tcp", function(applet)applet:send("hello world\n")end)core.register_service("hello_world_http", "http", function(applet)local response = "The path which was requested is: '" .. applet.path .. "'\n"applet:set_status(200)applet:add_header("content-length", string.len(response))applet:add_header("content-type", "text/plain")applet:start_response()applet:send(response)end)This defines two services:
- a simple hello_world_tcp service that will blindly respond to any TCP connection (so no headers/etc) with hello world.
- a more complicated HTTP example that will send a response with headers/etc.
-
Add the following lines to your load balancer configuration:
haproxygloballua-load /tmp/hello_world.luafrontend http_testbind 127.0.0.1:81mode httptcp-request inspect-delay 1shttp-request use-service lua.hello_world_httpfrontend tcp_testbind 127.0.0.1:82mode tcptcp-request inspect-delay 1stcp-request content use-service lua.hello_world_tcphaproxygloballua-load /tmp/hello_world.luafrontend http_testbind 127.0.0.1:81mode httptcp-request inspect-delay 1shttp-request use-service lua.hello_world_httpfrontend tcp_testbind 127.0.0.1:82mode tcptcp-request inspect-delay 1stcp-request content use-service lua.hello_world_tcp -
Restart the load balancer.
-
Make a request to the load balancer at
127.0.0.1:81
(i.e. view it in a web browser). It will show the URL that you requested.
Common Lua tasks Jump to heading
In this section, we demonstrate common ways to use Lua.
Verify a request with another service Jump to heading
Using Lua, you can have the load balancer validate some requests before servicing them.
-
Create a Lua script called
verify_request.lua
:verify_request.lualuacore.register_action("verify_request", { "http-req" }, function(txn)-- Verify that the request is authorized-- Obviously stupid in this case without additional information being sentlocal s = core.tcp()-- Should be pointing to a frontend with balancing/health checks/etc.s:connect("127.0.0.1:8080")-- We use HTTP 1.0 because we don't support keepalive or any other advanced features in this script.s:send("GET /verify.php?url=" .. txn.sf:path() .. " HTTP/1.1\r\nHost: veriy.example.com\r\n\r\n")local msg = s:receive("*l")-- Indicates a connection failureif msg == nil then-- This leave txn.request_verified unset for potentially different handlingreturnendmsg = tonumber(string.sub(msg, 9, 12)) -- Read code from 'HTTP/1.0 XXX'-- Makes it easy to test by making any file to be denied.if msg == 404 thentxn.set_var(txn,"txn.request_verified",true)elsetxn.set_var(txn,"txn.request_verified",false)end-- Read the response body, though in this example we aren't using it.msg = s:receive("*l")end)verify_request.lualuacore.register_action("verify_request", { "http-req" }, function(txn)-- Verify that the request is authorized-- Obviously stupid in this case without additional information being sentlocal s = core.tcp()-- Should be pointing to a frontend with balancing/health checks/etc.s:connect("127.0.0.1:8080")-- We use HTTP 1.0 because we don't support keepalive or any other advanced features in this script.s:send("GET /verify.php?url=" .. txn.sf:path() .. " HTTP/1.1\r\nHost: veriy.example.com\r\n\r\n")local msg = s:receive("*l")-- Indicates a connection failureif msg == nil then-- This leave txn.request_verified unset for potentially different handlingreturnendmsg = tonumber(string.sub(msg, 9, 12)) -- Read code from 'HTTP/1.0 XXX'-- Makes it easy to test by making any file to be denied.if msg == 404 thentxn.set_var(txn,"txn.request_verified",true)elsetxn.set_var(txn,"txn.request_verified",false)end-- Read the response body, though in this example we aren't using it.msg = s:receive("*l")end) -
Add a
lua-load
directive to theglobal
section of your configuration to load the Lua script:haproxygloballua-load /tmp/verify_request.luahaproxygloballua-load /tmp/verify_request.lua -
Add the following to the configuration inside the
frontend
where you wish to validate requests:haproxyfrontend examplehttp-request lua.verify_requesthttp-request deny if !{ var(txn.request_verified) -m bool }haproxyfrontend examplehttp-request lua.verify_requesthttp-request deny if !{ var(txn.request_verified) -m bool }
The Lua code sets a variable named txn.request_verified
that the load balancer can read. It then denies the request based on the value. You might also use the variable’s value to add a header, reduce a rate limit, or log the request.
Do you have any suggestions on how we can improve the content of this page?