Predicate locations

A predicate location name starts with '$' and is interpreted as a
string evaluated at runtime, and its value is used to route the
request into the location.  A predicate location matches if the
string evaluates to a non-empty value not equal to "0".  At each
nesting level, the first matching predicate location in the
configuration order is chosen.  Predicate locations are matched
after prefix and regex locations.  They are not matched if an exact
or a regex location matched, or if the longest matching prefix location
has the "^~" modifier.  Predicate locations can be nested inside
other location types and can contain nested locations of their own.

Example:

map $arg_foo $pred {
    bar  1;
    qux  1;
}

location $pred {
    root html;
}
This commit is contained in:
Roman Arutyunyan authored and Roman Arutyunyan committed 2026-08-26 15:14:20 +04:00
1 parent 8fa349cef4
commit 5f54125dde
4 files changed
+121 -20

No files matched your search

+2
View File
@@ -4473,12 +4473,14 @@ ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
#if (NGX_PCRE)
|| clcf->regex
#endif
|| clcf->predicate
|| clcf->noname)
{
if (plcf->vars.uri.len) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"proxy_pass\" cannot have URI part in "
"location given by regular expression, "
"or inside predicate location, "
"or inside named location, "
"or inside \"if\" statement, "
"or inside \"limit_except\" block");
+62 -4
View File
@@ -671,8 +671,8 @@ static ngx_int_t
ngx_http_init_locations(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
ngx_http_core_loc_conf_t *pclcf)
{
ngx_uint_t n;
ngx_queue_t *q, *locations, *named, tail;
ngx_uint_t n, p;
ngx_queue_t *q, *locations, *named, *predicate, tail;
ngx_http_core_loc_conf_t *clcf;
ngx_http_location_queue_t *lq;
ngx_http_core_loc_conf_t **clcfp;
@@ -695,6 +695,8 @@ ngx_http_init_locations(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
regex = NULL;
r = 0;
#endif
predicate = NULL;
p = 0;
for (q = ngx_queue_head(locations);
q != ngx_queue_sentinel(locations);
@@ -722,6 +724,16 @@ ngx_http_init_locations(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
#endif
if (clcf->predicate) {
p++;
if (predicate == NULL) {
predicate = q;
}
continue;
}
if (clcf->named) {
n++;
@@ -764,6 +776,29 @@ ngx_http_init_locations(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
ngx_queue_split(locations, named, &tail);
}
if (predicate) {
clcfp = ngx_palloc(cf->pool,
(p + 1) * sizeof(ngx_http_core_loc_conf_t *));
if (clcfp == NULL) {
return NGX_ERROR;
}
pclcf->predicate_locations = clcfp;
for (q = predicate;
q != ngx_queue_sentinel(locations);
q = ngx_queue_next(q))
{
lq = (ngx_http_location_queue_t *) q;
*(clcfp++) = lq->exact;
}
*clcfp = NULL;
ngx_queue_split(locations, predicate, &tail);
}
#if (NGX_PCRE)
if (regex) {
@@ -801,9 +836,17 @@ ngx_http_init_static_location_trees(ngx_conf_t *cf,
ngx_http_core_loc_conf_t *pclcf)
{
ngx_queue_t *q, *locations;
ngx_http_core_loc_conf_t *clcf;
ngx_http_core_loc_conf_t *clcf, **clcfp;
ngx_http_location_queue_t *lq;
if (pclcf->predicate_locations) {
for (clcfp = pclcf->predicate_locations; *clcfp; clcfp++) {
if (ngx_http_init_static_location_trees(cf, *clcfp) != NGX_OK) {
return NGX_ERROR;
}
}
}
locations = pclcf->locations;
if (locations == NULL) {
@@ -867,7 +910,7 @@ ngx_http_add_location(ngx_conf_t *cf, ngx_queue_t **locations,
#if (NGX_PCRE)
|| clcf->regex
#endif
|| clcf->named || clcf->noname)
|| clcf->predicate || clcf->named || clcf->noname)
{
lq->exact = clcf;
lq->inclusive = NULL;
@@ -966,6 +1009,21 @@ ngx_http_cmp_locations(const ngx_queue_t *one, const ngx_queue_t *two)
return ngx_strcmp(first->name.data, second->name.data);
}
if (first->predicate && !second->predicate) {
/* shift predicate locations to the end */
return 1;
}
if (!first->predicate && second->predicate) {
/* shift predicate locations to the end */
return -1;
}
if (first->predicate || second->predicate) {
/* do not sort the predicate locations */
return 0;
}
#if (NGX_PCRE)
if (first->regex && !second->regex) {
+54 -16
View File
@@ -1435,25 +1435,25 @@ ngx_http_update_location_config(ngx_http_request_t *r)
/*
* NGX_OK - exact or regex match
* NGX_OK - exact, regex or predicate match
* NGX_DONE - auto redirect
* NGX_AGAIN - inclusive match
* NGX_ERROR - regex error
* NGX_ERROR - regex or predicate error
* NGX_DECLINED - no match
*/
static ngx_int_t
ngx_http_core_find_location(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_http_core_loc_conf_t *pclcf;
ngx_int_t rc;
ngx_uint_t noregex;
ngx_http_core_loc_conf_t *clcf, *pclcf, **clcfp;
ngx_http_variable_value_t *vv;
#if (NGX_PCRE)
ngx_int_t n;
ngx_uint_t noregex;
ngx_http_core_loc_conf_t *clcf, **clcfp;
ngx_int_t n;
#endif
noregex = 0;
#endif
pclcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
@@ -1461,11 +1461,9 @@ ngx_http_core_find_location(ngx_http_request_t *r)
if (rc == NGX_AGAIN) {
#if (NGX_PCRE)
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
noregex = clcf->noregex;
#endif
/* look up nested locations */
@@ -1508,6 +1506,27 @@ ngx_http_core_find_location(ngx_http_request_t *r)
}
#endif
if (noregex == 0 && pclcf->predicate_locations) {
for (clcfp = pclcf->predicate_locations; *clcfp; clcfp++) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"test location: \"%V\"", &(*clcfp)->name);
vv = ngx_http_get_flushed_variable(r, (*clcfp)->predicate - 1);
if (vv && vv->len && (vv->len != 1 || vv->data[0] != '0')) {
r->loc_conf = (*clcfp)->loc_conf;
/* look up nested locations */
rc = ngx_http_core_find_location(r);
return (rc == NGX_ERROR || rc == NGX_DONE) ? rc : NGX_OK;
}
}
}
return rc;
}
@@ -3136,6 +3155,7 @@ ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
u_char *mod;
size_t len;
ngx_str_t *value, *name;
ngx_int_t index;
ngx_uint_t i;
ngx_conf_t save;
ngx_http_module_t *module;
@@ -3247,6 +3267,20 @@ ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
}
}
} else if (name->data[0] == '$') {
clcf->name = *name;
name->len--;
name->data++;
index = ngx_http_get_variable_index(cf, name);
if (index == NGX_ERROR) {
return NGX_CONF_ERROR;
}
clcf->predicate = index + 1;
} else {
clcf->name = *name;
@@ -3293,12 +3327,11 @@ ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
len = pclcf->name.len;
if (!clcf->predicate && !pclcf->predicate
#if (NGX_PCRE)
if (clcf->regex == NULL
&& ngx_filename_cmp(clcf->name.data, pclcf->name.data, len) != 0)
#else
if (ngx_filename_cmp(clcf->name.data, pclcf->name.data, len) != 0)
&& clcf->regex == NULL
#endif
&& ngx_filename_cmp(clcf->name.data, pclcf->name.data, len) != 0)
{
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"location \"%V\" is outside location \"%V\"",
@@ -3667,6 +3700,7 @@ ngx_http_core_create_loc_conf(ngx_conf_t *cf)
* clcf->error_pages = NULL;
* clcf->client_body_path = NULL;
* clcf->regex = NULL;
* clcf->predicate = 0;
* clcf->exact_match = 0;
* clcf->auto_redirect = 0;
* clcf->alias = 0;
@@ -4685,12 +4719,16 @@ ngx_http_core_root(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
sc.variables = n;
if (alias
&& (clcf->predicate
#if (NGX_PCRE)
if (alias && clcf->regex) {
|| clcf->regex
#endif
))
{
clcf->alias = NGX_MAX_SIZE_T_VALUE;
n = 1;
}
#endif
if (n) {
sc.cf = cf;
+3
View File
@@ -319,6 +319,8 @@ struct ngx_http_core_loc_conf_s {
ngx_http_regex_t *regex;
#endif
ngx_uint_t predicate; /* variable index + 1 */
unsigned noname:1; /* "if () {}" block or limit_except */
unsigned lmt_excpt:1;
unsigned named:1;
@@ -336,6 +338,7 @@ struct ngx_http_core_loc_conf_s {
#if (NGX_PCRE)
ngx_http_core_loc_conf_t **regex_locations;
#endif
ngx_http_core_loc_conf_t **predicate_locations;
/* pointer to the modules' loc_conf */
void **loc_conf;