]> git.kaiwu.me - nginx.git/commitdiff
Predicate locations
authorRoman Arutyunyan <arut@nginx.com>
Sun, 29 Mar 2026 06:47:49 +0000 (10:47 +0400)
committerRoman Arutyunyan <arutyunyan.roman@gmail.com>
Wed, 26 Aug 2026 11:14:20 +0000 (15:14 +0400)
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
src/http/ngx_http.c
src/http/ngx_http_core_module.c
src/http/ngx_http_core_module.h

index 43c2f24a39eb8e2b678efc145855796978f93083..28c530ac317337295d1875bbc42c30b32abbe6cd 100644 (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");
index a97cc35f18d47f34bdf2288b18a098fe08d5827f..06ea6eb8342519e29f11a8c36c841a57a9f552ae 100644 (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) {
index c6e20adcfb7f53c6d68552c77a353c618e2d7297..cfadaa7c0a33128769a714cfceb959efbec5a3b5 100644 (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;
index d07754b5be9ffab6040d67a4dd7df553c6ce22b5..21af1eb52c41cbac33c19a821fdba29a43bad3fa 100644 (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;