From 5f54125dde0d144476155d8964f6ec3d449f578d Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Sun, 29 Mar 2026 10:47:49 +0400 Subject: [PATCH] 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; } --- src/http/modules/ngx_http_proxy_module.c | 2 + src/http/ngx_http.c | 66 ++++++++++++++++++++-- src/http/ngx_http_core_module.c | 70 ++++++++++++++++++------ src/http/ngx_http_core_module.h | 3 + 4 files changed, 121 insertions(+), 20 deletions(-) diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c index 43c2f24a3..28c530ac3 100644 --- a/src/http/modules/ngx_http_proxy_module.c +++ b/src/http/modules/ngx_http_proxy_module.c @@ -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"); diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index a97cc35f1..06ea6eb83 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -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) { diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index c6e20adcf..cfadaa7c0 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -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; diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index d07754b5b..21af1eb52 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -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; -- 2.47.3