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:
22
NEWS
22
NEWS
@@ -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
|
||||
|
||||
|
||||
40
doc/pound.8
40
doc/pound.8
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
27
src/config.c
27
src/config.c
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -160,6 +160,7 @@
|
||||
"ECDHCurve"
|
||||
"Error"
|
||||
"ErrorFile"
|
||||
"FallThrough"
|
||||
"Family"
|
||||
"ForwardedHeader"
|
||||
"Grace"
|
||||
|
||||
@@ -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;
|
||||
|
||||
92
src/svc.c
92
src/svc.c
@@ -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".
|
||||
|
||||
@@ -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
67
tests/fallthrough.at
Normal 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
|
||||
@@ -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])
|
||||
|
||||
Reference in New Issue
Block a user