proxy_pass 末尾写不写路径,是两套规则:上线后接口路径莫名多了一段

一个订单服务从直连迁到网关后面。同一份后端代码,迁完之后前端开始 404,抓包看到后端收到的是 /api/order/list,而后端只认 /order/list。

网关配置长这样:

nginx 复制代码
upstream app {
    server 127.0.0.1:8080;
}

location /api/ {
    proxy_pass app/api/;
}

下面用 upstream 分组名 app 代替直接写地址,替换规则完全一样,读起来更容易对照。

运维的第一反应是「两边都写 /api/,正好对上」。这个判断错在一个前提上:他以为 location 那一段前缀会在转发时被剥掉,proxy_pass 里再把它加回去。实际行为不是「剥掉再拼」,而是「整体替换」。

文档里对这件事只有两句,但这两句描述的是两套完全不同的规则,分界线就是 proxy_pass 后面有没有路径部分。

文档是怎么区分这两种写法的?

nginx 的 proxy_pass 一节先给了一句总述,然后是两条并列的规则。带路径的那条:

If the proxy_pass directive is specified with a URI, then when a request is passed to the server, the part of a normalized request URI matching the location is replaced by a URI specified in the directive:

关键词是 replaced(替换)。请求 URI 中匹配 location 的那一部分,会被指令里写的 URI 整体换掉。

不带路径的那条:

If proxy_pass is specified without a URI, the request URI is passed to the server in the same form as sent by a client when the original request is processed, or the full normalized request URI is passed when processing the changed URI:

这种情况下前端发来的路径原样传给后端;如果中间改写过 URI,传的是改写之后完整的规范化路径。

两条规则套到同一个请求上,结果差别很大。把四种常见写法并排列出来:

配置写法 走哪条规则 请求 /api/order/list 时后端收到
proxy_pass app; 透传 /api/order/list
proxy_pass app/; 替换,匹配段换成 / /order/list
proxy_pass app/api/; 替换,匹配段换成 /api/ /api/order/list
proxy_pass app/v2/; 替换,匹配段换成 /v2/ /v2/order/list

第三行就是事故现场。location /api/ 匹配到的 /api/ 被替换成 /api/,看起来「没变」,于是前缀完整保留了下来,而后端根本没有 /api 这一层,自然 404。

真正的「剥掉前缀」效果是第二行,也就是 proxy_pass 后面只写一个斜杠。这一行的行为和很多人的直觉一致,但它不是默认行为,而是一个需要显式写出来的选择。

有人会问:第三行写和不写有什么区别?来源上看,/api/ 这个路径本来就来自 location,再写一遍等于告诉服务器「替换之后要保留它」。省略的话就变成第二行,前缀被斜杠覆盖掉。

为什么正则匹配的 location 里不能带路径?

文档在这一节的末尾补了一段例外:

In some cases, the part of a request URI to be replaced cannot be determined:

下面列了两种情况,其中一种是:

When location is specified using a regular expression, and also inside named locations.

紧接着的结论:

In these cases, proxy_pass should be specified without a URI.

原因很直接:替换规则要求「先确定哪一段要被换掉」。普通前缀匹配时这一段是明确的,就是 location 里那串字符;而正则匹配出来的结果是一个模式,能匹配多种实际路径,location 里那串字符并不是请求路径的子串,没法定位。

命名 location 同理------它本身不对应任何路径字符串,只是被 try_files、error_page 之类引用时的跳转目标。

所以配置写成 location ~ ^/api/(.*)$ { proxy_pass app/; } 时,那个斜杠是必须的,不是因为「这样更好看」,而是因为带路径的写法在这种 location 里根本没法计算。如果非要改写路径,得靠 rewrite。

中间改写路径之后又会怎样?

文档列的第二种情况是 location 内部用 rewrite 改过 URI、并且这条规则会继续用于处理当前请求(也就是带 break 的那种):

nginx 复制代码
location /name/ {
    rewrite    /name/([^/]+) /users?name=$1 break;
    proxy_pass app;
}

文档对这种情况的说明是:

In this case, the URI specified in the directive is ignored and the full changed request URI is passed to the server.

意思是:指令里写的路径被忽略,传给后端的是改写之后完整的请求 URI。

这条容易被忽略,因为它推翻了一个常见的组合想法------「用 rewrite 改一部分,再用 proxy_pass 的路径补另一部分」。这两件事不能叠加,break 之后 proxy_pass 的路径部分就不起作用了,只有一个例外:整个 URI 都被改写后的结果决定。

如果确实需要拼接,得在 rewrite 那一层把完整目标写出来,而不是指望转发时补。

迁移时怎么少出这类问题?

这套替换规则我整理成了一张对照表,把「location 写法、proxy_pass 写法、后端实际收到」三列并排,迁移前先按表核一遍。表格是用 AI墨衍 从几份网关配置里的实际失败案例倒推出来的,几个环境的配置模板共用一份。

具体做法是三步:先把前端实际请求的完整路径打出来;再按 location 能匹配到的最长前缀,算一下替换之后剩下什么;最后和后端的路由表对一遍。

第二步是最容易跳过的,因为大家都习惯「在心里算一下」,而这一步恰恰不能心算------location 匹配的是规范化之后的 URI,含双斜杠、含编码字符的请求会先被规范化,算出来的结果和肉眼看到的字面量未必一致。

举一个常见的:前端请求里带了 //(比如模板拼接时多了一个斜杠),nginx 规范化成单斜杠之后才去匹配 location,替换也是基于规范化后的结果。按字面量推,就会推出错误的结论。

还有一个更省事的手段:直接绕过网关打后端,把两个路径字符串打印出来比一比。网关和后端在同一个网络里时,这一步只需要改一次请求地址,比读配置快,也比猜准。

对比结果通常只有三种:路径一样,说明走的是透传,问题不在转发层;后端少了前缀,说明走了替换并且换成了斜杠;后端多了前缀,说明替换时把 location 的路径又写了一遍。三种情况对应三种改法,不用逐行读配置。

小结

三条结论。

一是 proxy_pass 后面有没有路径,决定了是「替换」还是「透传」,这不是风格问题,是语义分叉。二是「剥掉 location 前缀」需要显式写成只有一个斜杠,默认并不会剥。三是正则 location、命名 location、以及 rewrite ... break 之后,带路径的写法要么不合法,要么会被忽略。

排查这类问题时,先把请求的规范化路径写下来,再手工做一次替换,比盯着配置猜快得多。

相关推荐
IT枫斗者枫哥26 分钟前
用了BigDecimal还差一分?用四组Java代码查清原因
java·后端
时光不老杰29 分钟前
01 总体架构与模块边界
后端
贾公子33 分钟前
从单条内容到账号维度——AIGC 风险评分的工程实现
后端·python
一条泥憨鱼1 小时前
【消息队列】ACK确认了什么:消息队列可靠投递的完整链路
java·后端·kafka·消息队列·rabbitmq
孟陬3 小时前
Foobar 技术周刊#160:大模型让大规模形式化软件验证成为现实丨AI写C++代码之后会发生什么?丨我们真的需要alpha() 函数吗丨重试,有时会让故障更
前端·后端·typescript
BingoGo3 小时前
PHP 标准库的价值远不止少写几行代码
后端·php
JaguarJack3 小时前
PHP 标准库的价值远不止少写几行代码
后端·php·服务端
鶴哥只手遮天3 小时前
《Redis 8.10.3 深度剖析》系列 · 第 1 章·全景与源码地图:版本谱系、构建体系、全局状态与启动链路
redis·后端·开源
用户400315043353 小时前
《从 skynet.timeout 到回调执行:Skynet 分层时间轮源码剖析》
后端