1
0
mirror of https://github.com/graygnuorg/pound.git synced 2026-10-25 10:19:36 +03:00

Fall-through services

A fall-through service is defined for its side effects: modifying
the request or changing log suppression status. If it matches, its
statements are applied to the request and service selection process
continues from the next service.

* src/pound.h (SERVICE): New field: fall_through.
(POUND_HTTP): New field: log_suppress_mask.
* src/config.c (svc_defn): New statement: FallThrough.
(service_finalize): Make sure fall-through statements don't contain
backends or response rewrites.
* src/pound-mode.el: Recognize the FallThrough keyword.
* src/log.c (http_log): Use phttp->log_suppress_mask.
* src/svc.c (select_service): Rewrite. Don't break on finding a
matching fall-through service.
Apply log_suppress_mask bits from any matching service to the phttp
object.

* tests/fallthrough.at: New test.
* tests/Makefile.am: Add new test.
* tests/testsuite.at: Likewise.

* NEWS: Document changes.
* doc/pound.8: Likewise.
* doc/pound.texi: Likewise.
This commit is contained in:
Sergey Poznyakoff
2026-07-07 08:37:00 +03:00
parent 8e07ab2d15
commit 191c765cc4
12 changed files with 237 additions and 57 deletions

22
NEWS
View File

@@ -1,4 +1,4 @@
Pound -- history of user-visible changes. 2026-06-02
Pound -- history of user-visible changes. 2026-07-07
See the end of file for copying conditions.
Pound is a continuation of the software originally developed by
@@ -81,6 +81,26 @@ Cert, Ciphers, Disable. This requirement was lifted. Moreover, any
of these statements already imply that the backend is HTTPS capable,
which means that the HTTPS statement is superfluous if any of these
is used.
* Fall-through services
Fall-through services are processed specially. If the request matches
a fall-through service, any modification statements from that service
are applied to the request, and the selection process continues from
the next service. In other words, a fall-through service provides a
way to apply a set of modifications common for all services that follow.
A service is marked as a fall-through using the "FallThrough"
statement. Such services cannot contain backend definitions or
"Rewrite response" statements. An example of a fall-through service
follows:
Service
Path -beg "/status/"
SetHeader "Host: internal"
LogSuppress success
FallThrough
End
Version 4.23, 2026-05-26

View File

@@ -14,7 +14,7 @@
.\"
.\" You should have received a copy of the GNU General Public License
.\" along with pound. If not, see <http://www.gnu.org/licenses/>.
.TH POUND 8 "May 30, 2026" "pound" "System Manager's Manual"
.TH POUND 8 "July 7, 2026" "pound" "System Manager's Manual"
.SH NAME
pound \- HTTP/HTTPS reverse-proxy and load-balancer
.SH SYNOPSIS
@@ -1655,17 +1655,27 @@ Syntactically a tunnel is declared using the following construct:
\fBEnd\fR
.EE
.SH "Service"
A service is a definition of which backend servers
A service defines backend servers and conditions incoming requests
must match in order to be processed by one of these backends. It may
also modify the request before passing it on to the backend.
.PP
Services may be defined within a listener (in which case they will be
used only by that listener), or globally (which makes them available
to all listeners).
.PP
To select a service,
.B pound
will use to reply to incoming requests. A service may be defined as part
of a listener (in which case it will be used only by that listener), or
globally (which makes it available to all listeners).
.B Pound
selects a listener based on user-supplied conditions that analyze the
incoming request URL and/or headers. It will always scan
listener-specific services first. If none matches, it
will try the global ones. Services are tried in the same order as
they are defined in configuration.
iterates over a list of services defined in the listener. For each
service, it checks whether the request matches conditions defined
in it. If so, that service is selected. If none of the
per-listener services matched the request, the same process is applied
to the global service list. Finally, if no matching global service was
found as well, \fBpound\fR returns a 503 response.
.PP
Services marked as \fBFallThrough\fR (see below) are processed
specially. If the request matches such a service, any request
modification statements from the service are applied and the selection
process continues from the next service.
.PP
All configuration directives enclosed between
.B Service
@@ -2292,6 +2302,14 @@ name of a per-service constant coincides with the name of a
per-listener or a globally defined one, the former overrides the
latter for this service.
.TP
\fBFallThrough\fR [\fIbool\fR]
If \fIbool\fR is \fBtrue\fR or omitted, this statement instructs
\fBpound\fR to process this service as usual and continue service
selection. In other words, fall-through services are defined for side
effects, such as modifying the incoming request, changing log
suppression, etc. They cannot contain backend definitions or response
rewriting sections.
.TP
\fBIgnoreCase\fR \fIbool\fR
Override the global
.B IgnoreCase

View File

@@ -217,6 +217,7 @@ Service
* Service Logging::
* Backends::
* Session::
* FallThrough::
* Other Statements::
Backends
@@ -6562,6 +6563,7 @@ is used as its identifier. Service numbers start at 0.
* Service Logging::
* Backends::
* Session::
* FallThrough::
* Other Statements::
@end menu
@@ -7408,6 +7410,39 @@ longer than the specified number of seconds will be discarded.
This directive is mandatory.
@end deffn
@node FallThrough
@subsection FallThrough
@cindex fall-through service
@deffn {Session directive} FallThrough [@var{bool}]
Declares the service as a @dfn{fall-through}. The argument is optional and
defaults to @code{true}.
Fall-through services are defined for modifying incoming requests
before passing them on to another services. They cannot contain
backend definitions and @code{rewrite response} sections.
During request processing a matching fall-through service applies its
modification statements to the incoming request, after which service
selection continues.
@end deffn
The following example defines a fall-through service which applies to
any request whose path begins with @samp{/status/}. For each such
request, the @code{Host} header is set to @samp{internal} and logging
of successful responses is turned on:
@example
@group
Service "status"
Path -beg "/status/"
SetHeader "Host: internal"
LogSuppress success
FallThrough
End
@end group
@end example
@node Other Statements
@subsection Other Statements

View File

@@ -5467,6 +5467,14 @@ static CFG_DEFN svc_defn[] = {
.off = offsetof (SERVICE, disabled)
}
},
{
.name = "FallThrough",
.token = T_DIRECTIVE,
.vtype = CFG_TYPE_OPT_BOOL,
.rcvr = {
.off = offsetof (SERVICE, fall_through)
}
},
/* Set* statements */
{
@@ -8118,7 +8126,24 @@ service_finalize (SERVICE *svc, void *data)
be_count += be_setup.be_count;
}
if (be_count == 0)
if (svc->fall_through)
{
if (be_count)
{
conf_error_at_locus_range (&svc->locus,
"backends defined in"
" a FallThrough service");
return -1;
}
if (!SLIST_EMPTY (&svc->rewrite[REWRITE_RESPONSE]))
{
conf_error_at_locus_range (&svc->locus,
"Rewrite response defined in"
" a FallThrough service");
return -1;
}
}
else if (be_count == 0)
{
conf_error_at_locus_range (&svc->locus, "%s",
"warning: no backends defined");

View File

@@ -6534,8 +6534,7 @@ http_process_request (POUND_HTTP *phttp)
}
/*
* check that the requested URL still fits the old back-end (if
* any)
* Select the service to handle the request.
*/
if ((res = select_service (phttp)) != HTTP_STATUS_OK)
{

View File

@@ -1104,7 +1104,7 @@ http_log (POUND_HTTP *phttp)
if (http_log_format_check (lev))
return;
if (phttp->svc &&
(STATUS_MASK (phttp->response_code) & phttp->svc->log_suppress_mask))
(STATUS_MASK (phttp->response_code) & phttp->log_suppress_mask))
return;
prog = http_log_tab + lev;
if (SLIST_EMPTY (&prog->head))

View File

@@ -160,6 +160,7 @@
"ECDHCurve"
"Error"
"ErrorFile"
"FallThrough"
"Family"
"ForwardedHeader"
"Grace"

View File

@@ -966,6 +966,7 @@ typedef struct _service
char *sess_id; /* Session anchor ID */
SESSION_TABLE *sessions; /* currently active sessions */
int disabled; /* true if the service is disabled */
int fall_through;
int rewrite_errors; /* Rewrite HTTP errors. */
void *sctab; /* String constant table */
@@ -1170,7 +1171,7 @@ typedef struct _pound_http
backend */
int response_code;
int log_suppress_mask;
CONTENT_LENGTH res_bytes;
enum capture_state capturing;

View File

@@ -514,71 +514,83 @@ addr2str (char *res, int res_len, const struct addrinfo *addr, int no_port)
}
/*
* Match the request obtained via PHTTP against conditions from the service
* SVC. Return value:
*
* 0 request doesn't match or an error occurred;
* 1 request matches.
* -1 error
* Select from HEAD a service that matches current request in PHTTP,
* processing and skipping any FallThrough services.
*/
static int
match_service (SERVICE *svc, POUND_HTTP *phttp)
select_term_service (POUND_HTTP *phttp, SERVICE_HEAD *head)
{
int rc;
/*
* Temporarily assume this service. This is needed to properly
* expand %[remoteip 1] constructs, since TrustedIP lists can be declared
* inside a service.
*/
phttp->svc = svc;
rc = match_cond (&svc->cond, phttp, &phttp->request);
phttp->svc = NULL;
return rc;
}
/*
* Find the right service for a request and store it in phttp->svc.
* Return internal HTTP status code.
*/
int
select_service (POUND_HTTP *phttp)
{
SERVICE *svc;
int rc = 0;
SERVICE *svc;
SLIST_FOREACH (svc, &phttp->lstn->services, next)
SLIST_FOREACH (svc, head, next)
{
if (svc->disabled)
continue;
if ((rc = match_service (svc, phttp)) != 0)
break;
}
if (rc == 0)
{
/* try global services */
SLIST_FOREACH (svc, &services, next)
/*
* Temporarily assume this service. It will be reset below, if
* no service have been found or if an error occurred.
* This also makes sure any %[remoteip 1] constructs are properly
* expanded in match_cond (TrustedIP lists can be declared
* inside a service).
*/
phttp->svc = svc;
rc = match_cond (&svc->cond, phttp, &phttp->request);
if (rc > 0)
{
if (svc->disabled)
continue;
if ((rc = match_service (svc, phttp)) != 0)
/* Condition matches. */
phttp->log_suppress_mask |= svc->log_suppress_mask;
if (svc->fall_through)
{
/*
* For FallThrough conditions, apply any rewrites and
* continue, unless an error occurred.
*/
if (rewrite_apply (phttp, &phttp->request, REWRITE_REQUEST))
{
rc = -1;
break;
}
rc = 0;
}
else
break;
}
else if (rc < 0)
break;
}
switch (rc)
{
case -1:
phttp->svc = NULL;
return HTTP_STATUS_INTERNAL_SERVER_ERROR;
case 0:
phttp->svc = NULL;
return HTTP_STATUS_SERVICE_UNAVAILABLE;
}
phttp->svc = svc;
return HTTP_STATUS_OK;
}
/*
* Find the right service for the request and store it in phttp->svc.
* Return internal HTTP status code.
*/
int
select_service (POUND_HTTP *phttp)
{
int rc;
if ((rc = select_term_service (phttp, &phttp->lstn->services)) ==
HTTP_STATUS_SERVICE_UNAVAILABLE)
rc = select_term_service (phttp, &services);
return rc;
}
/*
* Calculate a uniformly distributed random number less than max.
* avoiding "modulo bias".

View File

@@ -94,6 +94,7 @@ TESTSUITE_AT = \
errfile.at\
error.at\
experr.at\
fallthrough.at\
fromfile.at\
headdeny.at\
header.at\

67
tests/fallthrough.at Normal file
View File

@@ -0,0 +1,67 @@
# This file is part of pound testsuite. -*- autotest -*-
# Copyright (C) 2026 Sergey Poznyakoff
#
# Pound is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Pound is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pound. If not, see <http://www.gnu.org/licenses/>.
AT_SETUP([FallThrough services])
AT_KEYWORDS([cond fallthrough])
PT_CHECK([
ListenHTTP
Service
SetHeader "FT-1: passed"
FallThrough
End
Service
Host "one"
Backend
End
End
Service
SetHeader "FT-2: passed"
FallThrough
End
Service
Host "two"
Backend
End
End
End
],
[GET /echo/foo
Host: one
end
200
x-orig-header-FT-1: passed
-x-orig-header-FT-2:
end
GET /echo/foo
Host: two
end
200
x-orig-header-FT-1: passed
x-orig-header-FT-2: passed
end
GET /echo/foo
Host: any
end
503
-x-orig-header-FT-1:
-x-orig-header-FT-2:
end
])
AT_CLEANUP

View File

@@ -92,6 +92,7 @@ m4_include([nacl.at])
m4_include([tbf.at])
m4_include([detcond.at])
m4_include([dti.at])
m4_include([fallthrough.at])
AT_BANNER([Lua])
m4_include([lua-load.at])