HAProxy Enterprise Documentation 2.0r1

Lua scripts

HAProxy Enterprise includes 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:

The basics

In the global section of your configuration file, define the Lua files that you want to load. HAProxy Enterprise reads these files before processing any chroot lines in the configuration, so they can be placed outside of your chrooted directory if needed.

HAProxy Enterprise has a non-blocking architecture and, becuase 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

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.

  1. Create a file named /etc/hapee-2.0/hello_world.lua with the following content:

    core.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.

  2. Add the following lines to your HAProxy Enterprise configuration:

    global
       lua-load /etc/hapee-2.0/hello_world.lua
    
    frontend http_test
       bind 127.0.0.1:81
       mode http
       tcp-request inspect-delay 1s
       http-request use-service lua.hello_world_http
    
    frontend tcp_test
       bind 127.0.0.1:82
       mode tcp
       tcp-request inspect-delay 1s
       tcp-request content use-service lua.hello_world_tcp
  3. Restart the load balancer.

  4. 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

In this section, we demonstrate common ways to use Lua.

Verify a request with another service

Using Lua, you can have HAProxy Enterprise validate some requests before servicing them.

  1. Create a Lua script called verify_request.lua:

     core.register_action("verify_request", { "http-req" }, function(txn)
       -- Verify that the request is authorized
       -- Obviously stupid in this case without additional information being sent
       local s = core.tcp()
    
       -- Should be pointing to an HAProxy 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 failure
       if msg == nil then
          -- This leave txn.request_verified unset for potentially diffrent handling
          return
       end
    
       msg = 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 then
          txn.set_var(txn,"txn.request_verified",true)
       else
          txn.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)
  2. Add a lua-load directive to the global section of your configuration to load the Lua script:

    global
       lua-load /etc/hapee-2.0/verify_request.lua
  3. Add the following to the HAProxy Enterprise configuration inside the frontend where you wish to validate requests:

    frontend example
       http-request lua.verify_request
       http-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.