Prev: Next: Up: Lua[Contents][Index]


8.5 Resending requests with Lua

As of version 4.23, pound makes it possible to resubmit a request if the selected backend wasn’t able to process it. There are two major cases: resending within the same service and sending the request to another service within the same listener.

Both cases are handled by a Lua function called via LuaModify statement in a Rewrite response section.

Resending to the Same Service

The usual scenario for the first case is as follows. Pound receives a request, selects the service and backend, and passes the request to that backend. The backend responds with a temporary error status, meaning that the request doesn’t satisfy some prerequisites for processing. Then pound calls a Lua function that analyzes the response, modifies the request so as to satisfy those prerequisites and marks the request for resending. The processing occurs within the same service, so normally the request will be sent to the same backend as previously, unless the backend has closed the connection after responding, in which case a new backend will be selected as described in Request balancing.

As an example of such a scenario, suppose that the backend responds with status 401 (‘Unauthorized’). In this case, the Lua function could look up the credentials, add the appropriate WWW-Authenticate header and then mark the request for resending. To do that, the function should assign a true value to the variable http.resend.

Below is an example implementation (module auth.lua). It uses three modules: base64, wwwauth, and creds. The first is available from https://luarocks.org/modules/iskolbin/base64.

The wwwauth module, described in detail in wwwauth.lua, provides a function for parsing the value of WWW-Authenticate HTTP header.

Finally, the creds module should provide the following function:

Method on creds: find (host, realm)

Look up credentials for the given host and realm. On success, return a pair: username, password. If not found, return nil.

Writing this module is left as an exercise for the reader.

Given these, the auth.lua module can be written as follows:

local M = {}

local b64 = require 'base64'
local wwwauth = require 'wwwauth'
local creds = require 'creds'

function M.inject()
   if http.resp.code == 401 then
      # Authorization is required. First, check if we have already
      # processed this request.
      if http.resendcount > 0 then
         return
      end
      # Parse the header:
      scheme, params = wwwauth.parse(http.resp.headers['WWW-Authenticate'])
      if scheme == 'Basic' then
         # Only Basic scheme is supported.  Look up for credentials:
         user, pass = creds.find(http.req.headers.host, params.realm)
         if user ~= nil then
            # Credentials found. Pack them into the Authorization
            # header.
            http.req.headers['Authorization'] = scheme .. " " ..
               b64.encode(user .. ':' .. pass)
            # Instruct pound to resend the request:
            http.resend = true
         end
      else
         pound.log(pound.INFO, "Unsupported authorization scheme: "..scheme)
      end
   end
end

return M

Notice the condition in line 11. The http.resendcount variable contains the number of resends the request has already underwent. Using it allows us to avoid retrying the already failed authentication attempt. However, even in absence of such check, pound will not allow to do more than 4 resends of the same request. This is a safety measure to avoid dead loops.

To use this module, add the following fragment to the Service section:

Rewrite response
    LuaModify "auth.inject"
End

This will work for requests that don’t have a body, such as GET, HEAD, and the like. For requests that do, such as POST or PUT, you will have to configure request content capturing as well. You do so using the ContentCapture statement. The statement takes a single numeric argument specifying maximum size of the request content. E.g.:

ContentCapture 65536

Selecting backend balancer weight

As noted above, the backend to serve the request is determined by pound. You can, however, affect its decision by defining the preferred balancing weight for the backend. Recall, that backends are grouped into balancer groups, each of which is assigned a unique numeric weight (see Balancer groups). When selecting the backend to use, each group is visited in turn, in order of increasing weight, until a suitable backend is found. You can instruct pound to start this process at a particular weight, instead of the default 0, by setting the http.balancer variable to the desired weight value. The most obvious use is to redirect the request to emergency backends (see emergency backends), which are assigned to the balancer group 65535. For example, if the selected backend responds with 421 (‘Misdirected Request’) status code, you can send it to your high availablility backend using the following approach.

First, define a Lua function that will select the emergency balancer group, if the backend returned 421:

function fallback()
  if http.resp.code == 421
    http.balancer = 65535
  end
end

Notice, that you need not set the http.resend variable: it is implied when you set http.balancer.

Then, construct your service section as follows:

Service
    Rewrite response
        LuaModify "fallback"
    End
    ContentCapture 16384
    Backend
        Address 10.1.0.1
        Port 80
    End
    Emergency
        Address 10.11.0.123
        Port 80
    End
End

Now, should the backend ‘10.1.0.1:80’ return 421, the request will be sent to ‘10.11.0.123:80’ for processing.

Notice, that it is only for the sake of an example that the above service definition contains one backend in each balancer group. It can, of course, contain any number of these, in which case pound will select one of them using http.balancer as a hint.

Resending to Another Service

Now let’s examine the second case: sending request to another service. This can be useful, for example, if the current service got a response from the backend indicating that it was unable to handle the request, and something in the response indicates that you have another service that should be able to handle the request properly. This too is done by defining a Lua function and calling it from Rewrite response section. However, instead of http.resend, one will use the http.service field.

This field is a table containing two keys: http.service.name contains the name of the currently selected service, and http.service.locus gives its location in the configuration file. The latter field is read-only, while the former can be set to indicate the service to resend the request to.

Initializing http.service.name to nil clears the currently selected service and instructs pound to select another service using the standard algorithm, described in Service Selection. You can control this process by adding or modifying request headers so as to trigger the desired condition.

Otherwise, you can initialize http.service.name with the symbolic name of the service you want to resend it to (see Service). For example, suppose there is a service whose definition within the listener starts with:

Service "default"

To have pound resend the request to that service, you would do the following in your Lua function:

http.service.name = "default"

If the service is defined in the global scope, use its name prefixed with a slash, i.e.:

http.service.name = "/default"

Notice, that the two variables http.service.name and http.resend are mutually exclusive. In other words, setting http.resend after assigning to http.service.name will clear the latter assignment and vice-versa.

To illustrate this, the following function instructs Pound to attempt a service named ‘fallback’ in case the backend returns 421 status code (‘Misdirected Request’):

function resend_421()
  if http.resp.code == 421
    http.service.name = "fallback"
  end
end

Example usage:

Service
    ContentCapture 65536
    Rewrite response
        LuaModify "resend_421"
    End
    ...
End

A final note: you can select both the service to use and the balancer group within it:

http.service.name = "fallback"
http.balancer = 65535

Prev: Next: Up: Lua[Contents][Index]