Last active
July 5, 2024 15:26
-
-
Save haproxytechblog/55d25a9af8c3c363cc0cc5c9477d864d to your computer and use it in GitHub Desktop.
5 Ways to Extend HAProxy with Lua
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ haproxy -vv | grep Lua | |
Built with Lua version : Lua 5.3.5 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
core.Debug("Hello HAProxy!\n") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
global | |
lua-load /path/to/hello.lua |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ haproxy -d -f /etc/haproxy/haproxy.cfg |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local function foo(txn, [param1], [param2], [etc.]) | |
-- perform logic here | |
end | |
core.register_fetches("foo_fetch", foo) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
txn.f:NAME_OF_FETCH() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
core.register_fetches("greater_than", function(txn, var1, var2) | |
local number1 = tonumber(txn:get_var(var1)) | |
local number2 = tonumber(txn:get_var(var2)) | |
if number1 > number2 then return true | |
else return false end | |
end) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
global | |
lua-load /path/to/greater_than.lua | |
frontend fe_main | |
bind :80 | |
# Store the threshold in a variable | |
http-request set-var(txn.connrate_threshold) int(100) | |
stick-table type ip size 1m expire 10s store conn_rate(10s) | |
http-request track-sc0 src | |
# Store the connection rate in a variable | |
http-request set-var(txn.conn_rate) src_conn_rate | |
# Deny if rate is greater than threshold | |
http-request deny if { lua.greater_than(txn.conn_rate,txn.connrate_threshold) -m bool } | |
default_backend be_servers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if { var(txn.connrate_threshold),sub(txn.conn_rate) -m int lt 0 } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local function backend_with_least_sessions(txn) | |
-- Get the frontend that was used | |
local fe_name = txn.f:fe_name() | |
local least_sessions_backend = "" | |
local least_sessions = 99999999999 | |
-- Loop through all the backends. You could change this | |
-- so that the backend names are passed into the function too. | |
for _, backend in pairs(core.backends) do | |
-- Look at only backends that have names that start with | |
-- the name of the frontend, e.g. "www_" prefix for "www" frontend. | |
if backend and backend.name:sub(1, #fe_name + 1) == fe_name .. '_' then | |
local total_sessions = 0 | |
-- Using the backend, loop through each of its servers | |
for _, server in pairs(backend.servers) do | |
-- Get server's stats | |
local stats = server:get_stats() | |
-- Get the backend's total number of current sessions | |
if stats['status'] == 'UP' then | |
total_sessions = total_sessions + stats['scur'] | |
core.Debug(backend.name .. ": " .. total_sessions) | |
end | |
end | |
if least_sessions > total_sessions then | |
least_sessions = total_sessions | |
least_sessions_backend = backend.name | |
end | |
end | |
end | |
-- Return the name of the backend that has the fewest sessions | |
core.Debug("Returning: " .. least_sessions_backend) | |
return least_sessions_backend | |
end | |
core.register_fetches('leastsess_backend', backend_with_least_sessions) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
global | |
lua-load /path/to/least_sessions.lua | |
frontend www | |
bind :80 | |
use_backend %[lua.leastsess_backend] | |
backend www_dc1 | |
balance roundrobin | |
server server1 192.168.10.5:8080 check maxconn 30 | |
backend www_dc2 | |
balance roundrobin | |
server server1 192.168.11.5:8080 check maxconn 30 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local function foo(value) | |
-- perform logic here | |
end | |
core.register_converters("foo_conv", foo) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local char_to_hex = function(c) | |
return string.format("%%%02X", string.byte(c)) | |
end | |
local function urlencode(url) | |
if url == nil then | |
return | |
end | |
url = url:gsub("\n", "\r\n") | |
url = url:gsub("([^%w ])", char_to_hex) | |
url = url:gsub(" ", "+") | |
return url | |
end | |
core.register_converters("urlencode", urlencode) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
global | |
lua-load /path/to/urlencode.lua | |
frontend fe_main | |
bind :80 | |
# URL encode the company name and store it in variable. | |
# In practice, you could get a company ID from a cookie | |
# or URL parameter and then find the name via a map file. | |
http-request set-var(req.company) str("Vinyl & Rare Music"),lua.urlencode | |
# Redirect to new URL | |
http-request redirect prefix http://%[req.hdr(Host)]/%[var(req.company)] if { var(req.company) -m found } { path / } | |
default_backend be_servers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local function foo(txn) | |
-- perform logic here | |
end | |
core.register_action("foo_action", { 'tcp-req', 'tcp-res', 'http-req', 'http-res' }, foo, 0) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
http-request lua.foo |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ sudo apt install -y python3 python3-flask |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import random | |
from flask import Flask | |
app = Flask(__name__) | |
@app.route("/<address>") | |
def check(address=None): | |
myrandom = random.randint(0, 1) | |
if myrandom > 0: | |
return 'allow' | |
else: | |
return 'deny' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ export FLASK_APP=ipchecker.py | |
$ flask run | |
* Serving Flask app "ipchecker" | |
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ curl http://127.0.0.1:5000/192.168.50.1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local function check_ip(txn, addr, port) | |
if not addr then addr = '127.0.0.1' end | |
if not port then port = 5000 end | |
-- Set up a request to the service | |
local hdrs = { | |
[1] = string.format('host: %s:%s', addr, port), | |
[2] = 'accept: */*', | |
[3] = 'connection: close' | |
} | |
local req = { | |
[1] = string.format('GET /%s HTTP/1.1', tostring(txn.f:src())), | |
[2] = table.concat(hdrs, '\r\n'), | |
[3] = '\r\n' | |
} | |
req = table.concat(req, '\r\n') | |
-- Use core.tcp to get an instance of the Socket class | |
local socket = core.tcp() | |
socket:settimeout(5) | |
-- Connect to the service and send the request | |
if socket:connect(addr, port) then | |
if socket:send(req) then | |
-- Skip response headers | |
while true do | |
local line, _ = socket:receive('*l') | |
if not line then break end | |
if line == '' then break end | |
end | |
-- Get response body, if any | |
local content = socket:receive('*a') | |
-- Check if this request should be allowed | |
if content and content == 'allow' then | |
txn:set_var('req.blocked', false) | |
return | |
end | |
else | |
core.Alert('Could not connect to IP Checker server (send)') | |
end | |
socket:close() | |
else | |
core.Alert('Could not connect to IP Checker server (connect)') | |
end | |
-- The request should be blocked | |
txn:set_var('req.blocked', true) | |
end | |
core.register_action('checkip', {'http-req'}, check_ip, 2) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
global | |
lua-load /path/to/ipchecker.lua | |
frontend fe_main | |
bind :80 | |
http-request lua.checkip 127.0.0.1 5000 | |
http-request deny if { var(req.blocked) -m bool } | |
default_backend be_servers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local function foo(applet) | |
-- perform logic here | |
end | |
core.register_service("foo_name", "[mode]", foo) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
frontend fe_proxy | |
http-request use-service lua.foo_name |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local function magic8ball(applet) | |
-- If client is POSTing request, receive body | |
-- local request = applet:receive() | |
local responses = {"Reply hazy", "Yes - definitely", "Don't count on it", "Outlook good", "Very doubtful"} | |
local myrandom = math.random(1, #responses) | |
local response = string.format([[ | |
<html> | |
<body>%s</body> | |
</html> | |
]], responses[myrandom]) | |
applet:set_status(200) | |
applet:add_header("content-length", string.len(response)) | |
applet:add_header("content-type", "text/html") | |
applet:start_response() | |
applet:send(response) | |
end | |
core.register_service("magic8ball", "http", magic8ball) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
global | |
lua-load /path/to/magic8ball.lua | |
frontend fe_main | |
bind :80 | |
http-request use-service lua.magic8ball if { path /magic } | |
default_backend be_servers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fe_main fe_main/<lua.magic8ball> 0/0/0/0/0 200 125 - - ---- 1/1/0/0/0 0/0 "GET /magic HTTP/1.1" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local function foo() | |
-- perform logic here | |
end | |
core.register_task(foo) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local function log_work() | |
while true do | |
core.Debug("Doing some task work!\n") | |
core.msleep(10000) | |
end | |
end | |
core.register_task(log_work) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment