bigywater.tech
微服务 · 29

路由匹配:前缀、正则、精确的优先级

nginx 的 location 到底怎么选?= 、^~ 、~ 、~* 和普通前缀的优先级?正则的顺序有影响吗?路径里的 // 和 %61 会怎样?macOS 和 Linux 上的大小写行为一样吗?这篇用 8 个 location 的矩阵逐项量出来。

G 对 C
/a/b/c/d 同时匹配前缀 /a/b/ 和正则 ~ ^/a/b/c 时,命中的是正则 G
D 对 E
/static/x.php 同时匹配 ^~ /static/ 和 ~ .php 结尾 时,命中的是 ^~ 前缀 D
A 对 H
/EXACT 在 macOS 的 nginx 1.31.6 上命中 = /exact(大小写不敏感),在 Linux 容器的 nginx 1.27.5 上落到 /

优先级

实测 · 本机执行输出
  请求路径          | 状态码 | 命中                      
  --------------+-----+-------------------------
  /exact        | 200 | A: = /exact             
  /exact/       | 200 | H: prefix /             
  /exact?x=1    | 200 | A: = /exact             
  /a/x          | 200 | B: prefix /a/           
  /a/b/x        | 200 | C: prefix /a/b/         
  /a/b/c/d      | 200 | G: regex ~ ^/a/b/c      
  /a/b/cx       | 200 | G: regex ~ ^/a/b/c      
  /static/x     | 200 | D: ^~ /static/          
  /static/x.php | 200 | D: ^~ /static/          
  /static/a.jpg | 200 | D: ^~ /static/          
  /x.php        | 200 | E: regex ~ .php 结尾      
  /a/x.php      | 200 | E: regex ~ .php 结尾      
  /a/x.png      | 200 | F: regex ~* .jpg/.png 结尾
  /pic.JPG      | 200 | F: regex ~* .jpg/.png 结尾
  /pic.jpg/     | 200 | H: prefix /             
  /A/b/c        | 200 | G: regex ~ ^/a/b/c      
  /nothing      | 200 | H: prefix /             
  几个值得注意的:/a/b/c/d 最长前缀是 C(/a/b/),但正则 G 匹配,正则胜过普通前缀;/a/x.png 同样是正则 F 胜过前缀 B;/static/x.php 命中 D 而不是 E,因为 ^~ 前缀一旦是最长前缀就不再试正则;/exact/ 不等于 /exact,落到 B 之外的 H;/A/b/c 在这台 macOS 上也命中了 G(~ 本应区分大小写,见第四节)

= 精确 > ^~ 最长前缀 > 正则(按顺序) > 普通最长前缀。

正则顺序

实测 · 本机执行输出
  配置顺序   | /api/v1/users.json 命中
  -------+----------------------
  R1 写在前 | R1: ^/api/           
  R2 写在前 | R2: .json 结尾         
  配置             | /api/v1/users.json 命中
  ---------------+----------------------
  前缀 /api/ 写在正则前 | R: .json 结尾          
  前缀 /api/ 写在正则后 | R: .json 结尾          
  前缀换成 ^~ /api/  | P: ^~ /api/          
  普通前缀 location 的先后顺序无关紧要(按前缀长度比),但正则的先后顺序决定谁胜出;普通前缀和正则谁先写也不影响“正则胜过前缀”;想让某个前缀不被正则抢走,要用 ^~

正则按文件里的顺序试;普通前缀的先后无关紧要。

规范化

实测 · 本机执行输出
  请求路径            | 状态码 | 命中           
  ----------------+-----+--------------
  /a/x            | 200 | B: prefix /a/
  //a//x          | 200 | B: prefix /a/
  /%61/x          | 200 | B: prefix /a/
  /a/./x          | 200 | B: prefix /a/
  /a/b/../x       | 200 | B: prefix /a/
  /a/%2e%2e/exact | 200 | A: = /exact  
  /a%2fx          | 200 | B: prefix /a/
  /exact%3Fx=1    | 200 | H: prefix /  
  /exact?x=1      | 200 | A: = /exact  
  nginx 在选 location 之前把 URI 规范化:合并连续的斜杠(merge_slashes 默认 on)、解码百分号(%61 = a)、去掉 . 和 .. 段;%2f(编码的斜杠)被解码成真正的斜杠还是留作字符,以及 %3F 是否被当成问号,都要看这一节的实际结果,不要凭想象

先合并斜杠、解码百分号、处理 . 和 .. 再匹配。

大小写

实测 · 本机执行输出
  请求路径          | macOS · nginx 1.31.6     | Linux 容器 · nginx/1.27.5 
  --------------+--------------------------+-------------------------
  /EXACT        | A: = /exact              | H: prefix /             
  /A/x          | B: prefix /a/            | H: prefix /             
  /A/B/x        | C: prefix /a/b/          | H: prefix /             
  /a/B/C/d      | G: regex ~ ^/a/b/c       | B: prefix /a/           
  /A/b/c        | G: regex ~ ^/a/b/c       | H: prefix /             
  /pic.JPG      | F: regex ~* .jpg/.png 结尾 | F: regex ~* .jpg/.png 结尾
  /x.PHP        | E: regex ~ .php 结尾       | H: prefix /             
  /STATIC/x.php | D: ^~ /static/           | E: regex ~ .php 结尾      
  据我对 nginx 文档的记忆,在大小写不敏感的操作系统(macOS、Cygwin、Windows)上前缀 location 的匹配会忽略大小写(这一句我没有在这次实验里核对文档原文);实测这台 macOS 上连 ~ 正则也不区分大小写(/A/b/c 命中了 ~ ^/a/b/c),而 Linux 容器里 ~ 区分大小写。在 macOS 上开发、在 Linux 上部署的配置,路径大小写相关的路由行为可能不一致

macOS 上前缀和 ~ 都不区分大小写,Linux 上区分。

分步演示

路由匹配
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / Go / nginx / Envoy / etcd / Consul / Nacos / OpenTelemetry / Prometheus(按各篇注明用到哪些及版本);所有服务都是本机自己起的进程,端口 9150 以上,只监听 127.0.0.1,不连任何外部服务,不碰本机已有的实例;故障用 kill -9 进程、让连接断开或在代码里注入延迟与错误来制造;计时用 time.perf_counter,数字每次运行会有小幅抖动。nginx 是 Homebrew 的 1.31.6(macOS);Linux 对照是本机 Docker Desktop 里的 nginx:1.27-alpine(nginx/1.27.5);每个 location 直接 return 自己的名字,不经过上游;因为两边的 nginx 版本也不同,第四节的差别不能完全归因于操作系统;请求用原始 socket 发出,避免 client 库对路径做规范化
    • 没有验证:嵌套 location、命名 location(@name)、try_files 与 location 的相互作用;if 块的怪异行为;server_name 的通配符与正则匹配;nginx 官方文档的大小写说明原文(上面是凭记忆引用,已在实验输出里注明)
    京ICP备2026067502号-1京公网安备11010502063487号