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