It's not easy to understand usage of Lua in HAProxy. The best way is a copy/paste of samples. This page will collect some samples of basic usages.
Note that this tuto is not:
In the following examples, I use the function print_r(). This function is usefull for displaying complex Lua structures. It is used for debugging. You can found this function in the page Lua scripts. The samples.lua file starts with an include (require()) of the print_r script.
require("print_r")
For testing the following samples, we always use HAProxy in debug mode. The
global section of the configuration file is like the following. This is not
a production ready configuration !
global lua-load samples.lua stats socket /tmp/haproxy.sock mode 644 level admin tune.ssl.default-dh-param 2048 defaults timeout client 1m timeout server 1m
The configuration file is named haproxy.cfg, all the Lua samples which could be copy, need to be pasted in the file samples.lua. The command line for starting sample is like the following. We assume a start from the directory containing the two configuration file (for default path reasons).
$ haproxy -d -f haproxy.cfg
Most of tests are performed whith Curl.
The documentation links points to the HAProxy version 1.7 documentation.
Note about sample-fetches wrappers and Lua Notation: txn.f is an array of wrapper functions. The object like members of an array doesn't supports the "." which is a reserved character. So all the sample fetches are replaced by a "_".
With Lua, a function is a variable. For convenience, the executed function can be declared inline as anonymous functions or referenced as named function. So the two sample below have the same behavior.
-- Named function
function a(arg)
print("hello")
end
register_callback(a)
-- Inline function
register_callback(function(arg)
print("hello")
end)
I will use the inline notation. Bellow the first sample commented. Note that all the Lua registered function are mapped in the HAProxy configuration with a prefix "lua.".
This first example is absolutely useless. It just shows how to create a Lua sample-fetch which return the same content that an embedded sample-fetch. By the way, it shows how to use embedded sample fetches in Lua.
First, HAProxy provides some functions and class. Some of these ones are availaible from the start of HAProxy. Where the lua file is read, it is executed, so the Lua file is executed during the start of HAProxy.
New all the functions which will be call by HAProxy (called "hook") needs to be registered. For declaring a Lua sample-fetch we will use the function core.register_fetches()
-- This register new sample-fetch called "lua.sample1"
core.register_fetches("sample1", function(txn)
-- By default, all varibles are global. specify the local
-- keyword to avoid bug. The following line store the
-- content of the sample-fetch "path" in the variable
-- "path".
local path = txn.f:path()
-- Print out the content of path variable. The data
-- id display on the HAProxy standard output.
print_r(path)
-- Return the value of the path as the sample-fetch
-- result.
return txn.f:path()
end)
We can use this new sample-fetch like the embedded sample-fetch"path":
listen sample1 mode http bind *:10010 acl use-lua lua.sample1 -m beg /use-lua acl use-php lua.sample1 -m beg /use-php http-request redirect location /lua if use-lua http-request redirect location /php if use-php http-request redirect location /other
Start HAProxy and test with curl:
$ curl -s -i http://127.0.0.1:10010/use-lua | grep Location
Location: /lua
$ curl -s -i http://127.0.0.1:10010/use-php | grep Location
Location: /php
$ curl -s -i http://127.0.0.1:10010/use-other | grep Location
Location: /other
In some cases, configurations have complex conditions based on sample-fetches. These kind of conditions are not easy to maintain because there are not easily understandable. We are quickly lost with a lot of ACL. A prod likely example is choosing redirect to https or no according with some conditions. The conditions are prod or preprod, already ssl or no, protected page or no. We want to force https for the payment page, except for the preprod, except if the request is already SSL and except if the DEBUG cookie is set.
We want to redirect to http if the request is ssl and if the preprod is required or if not a payment page is required and if the exception cookie is not set.
These condition are a little bit complex with classic ACL. The Lua can help us. The foolowing table resule input name, their definition and the testing method:
Condition name | Description | Test with Curl |
---|---|---|
is_payment_page | The payment page is requested | curl http://127.0.0.1:10020/payment/ |
is_ssl | The request is performed using SSL | curl -k https://127.0.0.1:10021/ |
is_preprod | The preprod is required | curl http://127.0.0.1:10020/ -H 'Host: preprod.test.com' |
is_cookie_exception | A DEBUG cookie is set | curl http://127.0.0.1:10020/ -H 'Cookie: DEBUG=1' |
Below the truth table resuming actions:
is_payment | is_ssl | is_preprod | is_debug | action |
---|---|---|---|---|
0 | 0 | 0 | 0 | forward |
0 | 0 | 0 | 1 | forward |
0 | 0 | 1 | 0 | forward |
0 | 0 | 1 | 1 | forward |
0 | 1 | 0 | 0 | HTTP redirect |
0 | 1 | 0 | 1 | HTTP redirect |
0 | 1 | 1 | 0 | HTTP redirect |
0 | 1 | 1 | 1 | HTTP redirect |
1 | 0 | 0 | 0 | HTTPS redirect |
1 | 0 | 0 | 1 | forward |
1 | 0 | 1 | 0 | forward |
1 | 0 | 1 | 1 | forward |
1 | 1 | 0 | 0 | forward |
1 | 1 | 0 | 1 | HTTP redirect |
1 | 1 | 1 | 0 | HTTP redirect |
1 | 1 | 1 | 1 | HTTP redirect |
Below the Lua code performing conditions. The code is split in two parts. The first part extract inputs, and the second part perform conditions based on inputs.
-- This function returns all condition in an array.
function get_variables(txn)
-- This array will contains conditions
local cond = {}
-- True if the path starts with "/payment/"
cond['is_payment_page'] = string.match(txn.sf:path(), '^/payment/') ~= nil
-- True if the front connection is SSL
cond['is_ssl'] = txn.f:ssl_fc() == 1
-- True if the domain name asked is preprod
cond['is_preprod'] = txn.f:req_fhdr('host') == 'preprod.test.com'
-- True if the cookie 'DEBUG' is set
cond['is_cookie_exception'] = txn.f:req_cook_cnt('DEBUG') >= 1
-- Display extracted conditions
-- print_r(cond)
return cond
end
-- This sample fetch return 1 if we need HTTPS redirect
core.register_fetches("sample2_1", function(txn)
-- Get input conditions
local cond = get_variables(txn)
-- Return result according with conditions value and policy.
if cond['is_ssl'] then return 0 end
if cond['is_cookie_exception'] then return 0 end
if cond['is_preprod'] then return 0 end
if cond['is_payment_page'] then return 1 end
return 0
end)
-- This sample fetch returns 1 if we need HTTP redirect
core.register_fetches("sample2_2", function(txn)
-- Get input conditions
local cond = get_variables(txn)
-- Return result according with conditions value and policy.
if not cond['is_ssl'] then return 0 end
if cond['is_cookie_exception'] then return 1 end
if cond['is_preprod'] then return 1 end
if not cond['is_payment_page'] then return 1 end
return 0
end)
And the HAProxy corresponding code:
listen sample2
mode http
bind *:10020
bind *:10021 ssl crt www.test.com.crt crt preprod.test.com.crt
http-request redirect location /to-https if { lua.sample2_1 1 }
http-request redirect location /to-http if { lua.sample2_2 1 }
http-request redirect location /forward