proxy_pass 末尾多一个斜杠,上游收到的路径会差多少?rewrite 的 last、break 到底怎么走?%2F、.. 和 // 经过网关会变成什么?这篇用回显路径的上游把它们逐格量出来,也纠正了一个我自己预先的想法。
配置 | /api/users?id=1 | /api/ | /api | /apix/y | /api//users -----------------------------------------+-----------------+-------+--------------------------+---------+------------ location /api/ + proxy_pass http://u | /api/users?id=1 | /api/ | 301 → http://x:PORT/api/ | 404 | /api//users location /api/ + proxy_pass http://u/ | /users?id=1 | / | 301 → http://x:PORT/api/ | 404 | /users location /api/ + proxy_pass http://u/v1/ | /v1/users?id=1 | /v1/ | 301 → http://x:PORT/api/ | 404 | /v1/users location /api + proxy_pass http://u/ | /users?id=1 | / | / | /x/y | /users location /api + proxy_pass http://u/v1 | /v1/users?id=1 | /v1/ | /v1 | /v1x/y | /v1/users location /api/ + proxy_pass http://u/v1 | /v1users?id=1 | /v1 | 301 → http://x:PORT/api/ | 404 | /v1users 看格子:proxy_pass 带 URI 时,用它替换请求路径里匹配 location 的那段前缀,其余部分接在后面,并且路径被规范化(连续斜杠合并);不带 URI 时原样转发整条路径(/api//users 原样到上游)。location /api(没有末尾斜杠)会把 /apix/y 也匹配进来,前缀 /api 被换掉后剩下的 x 接在 URI 后面(/x/y);location /api/ 配 proxy_pass http://u/v1(末尾没有斜杠)时,/api/users 变成 /v1users,前缀 /api/ 换成了 /v1,没有补斜杠。/api(不带斜杠)在 location /api/ 的配置下,nginx 自己回 301 补斜杠;/apix/y 不匹配 location /api/,落到没有 location 的默认处理(404)
带 URI 时替换匹配的前缀;不带 URI 时原样转发;末尾有没有斜杠差别很大。
标志 | 请求 /old/a/b?x=1 的结果(上游收到的路径,或 nginx 自己的响应) ----------+------------------------------------------- (无标志) | hit /new/ location break | /new/a/b?x=1 last | hit /new/ location redirect | 302 → http://x:PORT/new/a/b?x=1 permanent | 301 → http://x:PORT/new/a/b?x=1 没有标志时,rewrite 改了 URI 之后,当前 location 里剩下的 rewrite 模块指令(rewrite、return、set、if)继续执行,然后 nginx 用改写后的 URI 重新选 location——效果和 last 一样,所以请求命中 /new/,根本没到上游(我原以为会接着执行 proxy_pass,实测不是);只有 break 才会停在当前 location,用改写后的 URI 去 proxy_pass(上游收到 /new/a/b?x=1);redirect / permanent 返回 302 / 301 让 client 重新请求。查询串默认保留
没有标志时改了 URI 就重新选 location(像 last);break 才会继续 proxy_pass。
写法 | /api/a%20b?q=%41 | /api/a%2Fb | /api/x/../y | /api//z --------------------------------------------------------+------------------+------------+-------------+-------- (a) location /api/ + proxy_pass http://u | /api/a%20b?q=%41 | /api/a%2Fb | /api/x/../y | /api//z (b) location /api/ + proxy_pass http://u/ | /a%20b?q=%41 | /a/b | /y | /z (c) rewrite ^/api/(.*)$ /$1 break + proxy_pass http://u | /a%20b?q=%41 | /a/b | /y | /z 不带 URI 的 proxy_pass 原样转发请求里的路径(%2F、.. 段、// 都保持原样);带 URI 的 proxy_pass 和 rewrite 之后转发的是 nginx 处理过的路径:.. 被解析掉、// 被合并、%2F 被解码成了真正的斜杠(/api/a%2Fb 变成 /a/b),%20 仍保持编码。网关和上游对路径的理解不一致(例如上游看到的是 %2F 还是 /),是路径穿越和缓存投毒类问题的来源之一
带 URI 的 proxy_pass 与 rewrite 之后会解码 %2F、解析 .. 和 //。