NGINX 1.31.5 Predicate Location:从按 URL 路由到按条件路由

NGINX 1.31.5 引入了 Predicate Location(谓词 Location),让 location 不再只能根据 URI 匹配,还可以根据变量的值选择路由。

传统 NGINX 的路由核心是 URI:

location /api {
    proxy_pass http://backend;
}

而 Predicate Location 可以直接使用变量:

location $post_requests {
    proxy_pass http://post_backend;
}

当变量值非空且不等于 0 时,该 Predicate Location 即可命中。

这意味着 NGINX 的路由条件不再局限于 URL,还可以来自请求方法、Header、Cookie、来源 IP,以及其他能够计算为变量的请求特征。

例如,先通过 map 计算条件,再通过 Predicate Location 选择对应的完整处理配置:

map $request_method $post_requests {
    POST    1;
    default 0;
}

location $post_requests {
    proxy_pass http://post_backend;
}

请求体 JSON 路由

更值得关注的是,Predicate Location 还可以结合提前读取请求体和 JSON 变量解析能力,在 Location 匹配阶段直接使用请求体中的业务数据。

例如:

{
    "user": {
        "params": {
            "vip": 1
        }
    }
}

可以提前读取请求体并提取 JSON 字段:

http {
    client_body_early_read on;

    json_set $vip $request_body user.params.vip;

    server {
        location $vip {
            return 200 "VIP user\n";
        }
    }
}

这样,API 网关可以直接根据 JSON 参数、租户、用户类型等业务信息进行路由,而无需额外引入 NJS、Lua 或后端网关完成第一次业务判断。

Location 匹配优先级

Predicate Location 并不会取代原有 URI Location,而是增加了一层新的匹配能力。

同一层 Location 的匹配顺序大致为:

  1. 精确匹配 location = /path
  2. 查找最长前缀 Location
  3. 如果前缀 Location 未使用 ^~,按配置顺序匹配正则 Location
  4. 正则未命中时,按配置顺序匹配 Predicate Location
    多个 Predicate Location 同时满足条件时,按配置顺序优先匹配。
  5. 都未命中,则回退到最长前缀 Location

没有 Predicate Location 之前

在 Predicate Location 出现之前,NGINX 同样可以根据请求特征进行判断,但条件计算路由处理通常是分离的。

常见方案主要有以下几种。

1. map + proxy_pass:简单场景最推荐

如果需求只是根据条件选择不同 upstream,其实不需要新的 Location。
例如根据请求方法选择后端:

map $request_method $backend {
    POST    http://post_backend;
    default http://default_backend;
}

server {
    location /api/ {
        proxy_pass $backend;
    }
}

也可以根据 Header 分流:

map $http_x_tenant $backend {
    vip     http://vip_backend;
    default http://normal_backend;
}

这种方式适合:

  • 请求方法分流
  • Header 分流
  • Cookie 分流
  • IP 分流
  • 灰度发布
  • 简单租户路由

这是传统 NGINX 中最常见、也最干净的方案,但它有一个明显特点:条件决定的是变量值,而不是完整的 Location 配置块。

如果只是切换 upstream,非常合适;如果不同请求需要完全不同的限流、日志、缓存、超时和鉴权策略,配置会逐渐碎片化。

例如:

map
├── backend
├── timeout
├── cache
├── rate
└── log

业务策略越复杂,需要维护的变量越多。

2. if:简单判断可以使用

例如:

location /api/ {
    if ($request_method = POST) {
        proxy_pass http://post_backend;
    }

    proxy_pass http://default_backend;
}

问题在于,NGINX 的 if 并不是普通编程语言中的流程控制语句,而是 Rewrite 模块的一部分。
因此社区长期流传:If is evil.

简单的 Rewrite、return 等场景通常没有问题,但不建议使用 if 构建复杂的业务流程和路由逻辑,否则可能出现难以理解的配置行为。

3. 内部跳转:间接选择不同处理 Location

传统 NGINX 也可以通过:

  • try_files
  • error_page
  • rewrite
  • 命名 Location
  • 内部重定向

等机制间接进入不同的处理 Location。

这种方案本质上可以实现“条件决定处理配置”,但配置通常比较绕。

主要问题包括:

  • 条件判断与跳转逻辑分散
  • 不同指令支持的跳转方式不同
  • 内部重定向可能改变 URI
  • 配置可读性和维护成本较高

因此更适合已有内部跳转需求,而不是单纯为了实现条件路由。

4. NJS / Lua:复杂业务逻辑

如果条件涉及复杂计算,例如:

  • 多个 Header
  • Cookie
  • IP 库
  • JSON Body
  • 数据库
  • Redis
  • 动态规则

以前通常需要引入脚本。

使用 NJS:

location /api/ {
    js_content route_handler;
}

或者使用 OpenResty 的 Lua:

location /api/ {
    content_by_lua_block {
        -- 判断条件
        -- 选择处理逻辑
    }
}

优点是灵活,几乎可以实现任意逻辑。

缺点也比较明显:

  • 增加运行时依赖
  • 配置不再是纯 NGINX
  • 调试复杂度提高
  • Lua 通常意味着进入 OpenResty 生态
  • 脚本逻辑可能成为新的性能和维护边界

5. 根据 JSON 请求体路由:以前最麻烦

例如请求:

{
    "user": {
        "params": {
            "vip": 1
        }
    }
}

希望根据:

user.params.vip

决定请求进入哪个后端。

传统 NGINX 的处理顺序通常是:

请求进入
    ↓
Location 匹配
    ↓
读取 Request Body
    ↓
内容处理

也就是说:Location 选择通常发生在请求体读取之前。

因此,标准 NGINX 很难直接根据 JSON Body 选择 Location。

以前通常只能选择:

方案 A:NJS 解析 JSON
方案 B:Lua / OpenResty
方案 C:代理到 API Gateway
方案 D:后端应用自己判断

这意味着第一次业务路由需要离开 NGINX 的原生配置体系。

Predicate Location 示例

下面通过一个租户路由场景看看它真正解决了什么问题。

假设所有请求都访问:

POST /api/send

但 VIP 租户需要:

  • 独立限流
  • 独立日志
  • 独立超时
  • 独立 Header
  • 独立 Backend

普通租户则使用默认策略。

首先计算租户条件:

map $http_x_tenant_type $vip_tenant {
    vip     1;
    default 0;
}

然后直接使用 Predicate Location:

server {
    # VIP 请求
    location $vip_tenant {
        limit_req zone=vip_limit;
        access_log /var/log/nginx/vip.log;
        proxy_connect_timeout 1s;
        proxy_set_header X-Service-Tier VIP;
        proxy_pass http://vip_backend;
    }

    # 普通请求
    location /api/send {
        limit_req zone=normal_limit;
        access_log /var/log/nginx/normal.log;
        proxy_connect_timeout 5s;
        proxy_pass http://normal_backend;
    }
}

请求:

POST /api/send

X-Tenant-Type: vip

处理流程:

Request
   ↓
URI:/api/send
   ↓
计算 $vip_tenant
   ↓
vip → 1
   ↓
Predicate Location 命中
   ↓
VIP 独立处理策略
   ├── VIP 限流
   ├── VIP 日志
   ├── VIP 超时
   ├── VIP Header
   └── VIP Backend

Predicate Location 真正改变了什么?

Predicate Location 最大的价值并不是让 NGINX “终于可以判断条件”。此前,mapif、NJS、Lua 都可以完成条件计算。

真正的变化在于:以前可以计算条件,但条件通常只能修改变量,再通过 proxy_pass 等指令影响后续处理;Predicate Location 则让条件直接决定请求进入哪一个完整的 Location 配置块。

传统模式通常是:

请求 → URI Location → 计算条件 → 修改变量 → 同一个 Location 继续处理

例如,map + proxy_pass 可以根据请求方法、Header 或 Cookie 选择不同 upstream,这种方式简单高效,适合“请求发给谁”不同的场景。

Predicate Location 则进一步把条件判断变成路由入口:

请求特征 → map / JSON 解析 → Predicate Location → 对应处理策略

命中不同 Predicate Location 后,请求可以直接进入不同的 NGINX 配置,包括独立的限流、鉴权、缓存、日志、超时和 Upstream。

因此可以简单理解为:

map 解决“条件怎么计算”,proxy_pass 解决“请求发给谁”,Predicate Location 解决“满足条件后应该进入哪套完整的 NGINX 处理策略”。

如果只是切换后端,传统的 map + proxy_pass 已经足够;如果需要根据业务条件切换一整套 NGINX 处理策略,Predicate Location 的价值就真正体现出来了。

从这个角度看,NGINX 正从传统的“按地址路由”,进一步发展为“按请求事实路由”。

如果你有魔法,你可以看到一个评论框~