Some samples about usage of Lua with HAProxy
It's not easy to understand usage of Lua in HAProxy. The best
way is a cpy paste of samples. This page will collect some
samples of basic usages.
Note that this tuto is not:
- HAProxy configuraiton tutorial. The reader must known HAProxy basics
- Lua programation tutorial. The reader must known Lua basics
Useful links
Downloads
Sumary
Before start
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 "_". All the sample-fetches wrapper for the HAProxy version 1.7 are:
» Display / hide sample-fetches wrapper names
| always_false | always_true | avg_queue | base | base32 | base32_src
| | be_conn | be_id | be_name | be_sess_rate | capture_req_hdr | capture_req_method
| | capture_req_uri | capture_req_ver | capture_res_hdr | capture_res_ver | connslots | cook
| | cook_cnt | cookie | cook_val | date | dst | dst_conn
| | dst_is_local | dst_port | env | fc_fackets | fc_lost | fc_rcvd_proxy
| | fc_reordering | fc_retrans | fc_rtt | fc_rttvar | fc_sacked | fc_unacked
| | fe_conn | fe_id | fe_name | fe_req_rate | fe_sess_rate | hdr
| | hdr_cnt | hdr_ip | hdr_val | http_auth | http_auth_group | http_first_req
| | int | ipv4 | ipv6 | method | nbproc | nbsrv
| | path | payload | payload_lv | proc | query | queue
| | rand | rdp_cookie | rdp_cookie_cnt | rep_ssl_hello_type | req_body | req_body_len
| | req_body_param | req_body_size | req_cook | req_cook_cnt | req_cook_val | req_fhdr
| | req_fhdr_cnt | req_hdr | req_hdr_cnt | req_hdr_ip | req_hdr_names | req_hdr_val
| | req_len | req_payload | req_payload_lv | req_proto_http | req_rdp_cookie | req_rdp_cookie_cnt
| | req_ssl_ec_ext | req_ssl_hello_type | req_ssl_sni | req_ssl_st_ext | req_ssl_ver | req_ver
| | res_comp | res_comp_algo | res_cook | res_cook_cnt | res_cook_val | res_fhdr
| | res_fhdr_cnt | res_hdr | res_hdr_cnt | res_hdr_ip | res_hdr_names | res_hdr_val
| | res_len | res_payload | res_payload_lv | resp_ver | res_ssl_hello_type | res_ver
| | sc0_bytes_in_rate | sc0_bytes_out_rate | sc0_clr_gpc0 | sc0_conn_cnt | sc0_conn_cur | sc0_conn_rate
| | sc0_get_gpc0 | sc0_get_gpt0 | sc0_gpc0_rate | sc0_http_err_cnt | sc0_http_err_rate | sc0_http_req_cnt
| | sc0_http_req_rate | sc0_inc_gpc0 | sc0_kbytes_in | sc0_kbytes_out | sc0_sess_cnt | sc0_sess_rate
| | sc0_tracked | sc0_trackers | sc1_bytes_in_rate | sc1_bytes_out_rate | sc1_clr_gpc0 | sc1_conn_cnt
| | sc1_conn_cur | sc1_conn_rate | sc1_get_gpc0 | sc1_get_gpt0 | sc1_gpc0_rate | sc1_http_err_cnt
| | sc1_http_err_rate | sc1_http_req_cnt | sc1_http_req_rate | sc1_inc_gpc0 | sc1_kbytes_in | sc1_kbytes_out
| | sc1_sess_cnt | sc1_sess_rate | sc1_tracked | sc1_trackers | sc2_bytes_in_rate | sc2_bytes_out_rate
| | sc2_clr_gpc0 | sc2_conn_cnt | sc2_conn_cur | sc2_conn_rate | sc2_get_gpc0 | sc2_get_gpt0
| | sc2_gpc0_rate | sc2_http_err_cnt | sc2_http_err_rate | sc2_http_req_cnt | sc2_http_req_rate | sc2_inc_gpc0
| | sc2_kbytes_in | sc2_kbytes_out | sc2_sess_cnt | sc2_sess_rate | sc2_tracked | sc2_trackers
| | sc_bytes_in_rate | sc_bytes_out_rate | sc_clr_gpc0 | sc_conn_cnt | sc_conn_cur | sc_conn_rate
| | sc_get_gpc0 | sc_get_gpt0 | sc_gpc0_rate | sc_http_err_cnt | sc_http_err_rate | sc_http_req_cnt
| | sc_http_req_rate | sc_inc_gpc0 | sc_kbytes_in | sc_kbytes_out | scook | scook_cnt
| | scook_val | sc_sess_cnt | sc_sess_rate | sc_tracked | sc_trackers | set_cookie
| | shdr | shdr_cnt | shdr_ip | shdr_val | so_id | src
| | src_bytes_in_rate | src_bytes_out_rate | src_clr_gpc0 | src_conn_cnt | src_conn_cur | src_conn_rate
| | src_get_gpc0 | src_get_gpt0 | src_gpc0_rate | src_http_err_cnt | src_http_err_rate | src_http_req_cnt
| | src_http_req_rate | src_inc_gpc0 | src_is_local | src_kbytes_in | src_kbytes_out | src_port
| | src_sess_cnt | src_sess_rate | src_updt_conn_cnt | srv_conn | srv_id | srv_is_up
| | srv_sess_rate | ssl_bc | ssl_bc_alg_keysize | ssl_bc_cipher | ssl_bc_protocol | ssl_bc_session_id
| | ssl_bc_unique_id | ssl_bc_use_keysize | ssl_c_ca_err | ssl_c_ca_err_depth | ssl_c_der | ssl_c_err
| | ssl_c_i_dn | ssl_c_key_alg | ssl_c_notafter | ssl_c_notbefore | ssl_c_s_dn | ssl_c_serial
| | ssl_c_sha1 | ssl_c_sig_alg | ssl_c_used | ssl_c_verify | ssl_c_version | ssl_fc
| | ssl_fc_alg_keysize | ssl_fc_alpn | ssl_fc_cipher | ssl_fc_has_crt | ssl_fc_has_sni | ssl_fc_is_resumed
| | ssl_fc_npn | ssl_fc_protocol | ssl_fc_session_id | ssl_fc_sni | ssl_fc_unique_id | ssl_fc_use_keysize
| | ssl_f_der | ssl_f_i_dn | ssl_f_key_alg | ssl_f_notafter | ssl_f_notbefore | ssl_f_s_dn
| | ssl_f_serial | ssl_f_sha1 | ssl_f_sig_alg | ssl_f_version | status | stopping
| | str | table_avl | table_cnt | unique_id | url | url32
| | url32_src | url_ip | urlp | url_param | url_port | urlp_val
| | wait_end
|
» Display / hide converters wrapper names
base64 | bool | bytes | capture_req | capture_res | cpl |
crc32 | djb2 | even | hex | http_date |
in_table | ipmask | language | lower | ltime |
neg | not | odd | sdbm | table_bytes_in_rate |
table_bytes_out_rate | table_conn_cnt | table_conn_cur | table_conn_rate | table_gpc0 |
table_gpc0_rate | table_gpt0 | table_http_err_cnt | table_http_err_rate | table_http_req_cnt |
table_http_req_rate | table_kbytes_in | table_kbytes_out | table_server_id | table_sess_cnt |
table_sess_rate | table_trackers | upper | url_dec | utime |
wt6 |
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.".
Using HAProxy sample fetches inside Lua
This first example is absolutely useless. It just shows how creating a Lua
sample-fetch which return the same content that an embedded sample-fetch.
By the way, it shows how using 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()
-
core.register_fetches(): Function
(doc):
Register Lua sample-fetch. register_fetches takes a function as
argument. This function will be called according with the HAProxy
-
txn: Class TXN
(doc):
Provides functions available with the transaction.
-
txn.f: Class Fetches
(doc):
Provides an access to all the haproxy sample fetches. There are called
sample-fetches wrappers.
-- 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
Simplify complex conditions
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
|