📄nginx的try_files验证
2021-7-2
| 2023-4-9
0  |  0 分钟
type
status
date
slug
summary
tags
category
icon
password

1. 实验环境:

  • docker nginx配置192.168.123.20:8080->80/tcp, 192.168.123.20:8081->443/tcp
  • Windows /etc/hosts绑定192.168.123.20
  • 主要验证try_files的功能,猜想仅供参考
#docker run的参考,没有用dockerfile,只是实验性质,run就完了,不建议用--rm,不好debug docker container run \\ --name mynginx \\ -v "$PWD/html":/usr/share/nginx/html \\ -v "$PWD/conf":/etc/nginx \\ -v /etc/localtime:/etc/localtime \\ -p 192.168.123.20:8080:80 \\ -p 192.168.123.20:8081:443 \\ -dit \\ nginx

2. 实验步骤:

1. 说明

由于是docker运行,涉及端口转发,正好模拟了client和server端口不一致的情景,配置对比教程多了absolute_redirect off,即关闭绝对重定向,使用相对重定向。 另外,和http/https协议无关,都会出现重定向。 如果不关闭绝对重定向,那下面例子1访问https://t.e1cent.top:8081/a 会导致请求变成https://t.e1cent.top/a/ ,即端口号变成真实的,通常会出错

2. 例子1:配置"try_files $uri $uri/"

关键配置
absolute_redirect off; root /usr/share/nginx/html; index index.html; location = /a/index.html { default_type text/plain; return 200 hello; } location = /foo.html { default_type text/plain; return 200 bar; } location / { #try_files $uri $uri/index.html /foo.html; try_files $uri $uri/ /foo.html; }
[root@e1zone nginx-docker-demo]# tree html/a/ html/a/ └── index.htmlbak 0 directories, 1 file [root@e1zone nginx-docker-demo]# cat html/a/index.htmlbak <h1>This is a , Hello World</h1>
  • a/index.html不存在,访问https://t.e1cent.top:8081/a ,注意尾部不带斜杠 结果:先301再403。 猜想:先匹配“try_files $uri/”,由于请求不带斜杠引发重定向,导致请求变成https://t.e1cent.top:8081/a/ ,此时匹配的规则是“index index.html;”,
location / { #try_files $uri $uri/index.html /foo.html; try_files $uri $uri/ /foo.html; } 其实就是下面的 location / { root /usr/share/nginx/html; index index.html; #try_files $uri $uri/index.html /foo.html; try_files $uri $uri/ /foo.html; }
但是a/index.html不存在,只能403。为什么不继续匹配/foo.html规则,即请求变成https://t.e1cent.top:8081/foo.html ,暂不清楚
notion image
notion image
  • a/index.html不存在,访问https://t.e1cent.top:8081/a/ ,注意尾部带斜杠 结果:直接403。 猜想:先匹配“try_files $uri/”,再匹配“index index.html;”
  • a/index.html存在,访问https://t.e1cent.top:8081/a/index.html 结果:直接返回hello。 猜想:先匹配“location = /a/index.html ”
  • a/index.html存在,访问https://t.e1cent.top:8081/a ,注意尾部不带斜杠 结果:先301再返回hello。 猜想:先匹配““try_files $uri $uri/ ”导致请求变成https://t.e1cent.top:8081/a/ ,结果“ index index.html;”指示请求要变成https://t.e1cent.top:8081/a/index.html ,接着匹配到“location = /a/index.html”。除了301,后面的步骤在浏览器上没有体现。
  • a/index.html存在,访问https://t.e1cent.top:8081/a/ ,注意尾部带斜杠 结果:直接返回hello。 猜想:先匹配““try_files $uri $uri/ ”,结果“ index index.html;”指示请求要变成https://t.e1cent.top:8081/a/index.html ,接着匹配到“location = /a/index.html”
  • a/index.html存在,访问https://t.e1cent.top:8081/a/index.html 结果:直接返回hello。 猜想:“location = /a/index.html ”优先级高
  • c目录不存在,访问https://t.e1cent.top:8081/c 或者https://t.e1cent.top:8081/c/ 结果:返回bar。 猜想:因为c物理文件/目录不存在,且“location =”规则没有指示,此时请求直接匹配到最后的“try_files $uri $uri/ /foo.html;”,请求变成 https://t.e1cent.top:8081/foo.html ,即$uri被替换
网上查到的location的优先级 (location = ) > (location 完整路径 ) > (location ^~ 路径) > (location ~,~* 从上向下正则顺序,匹配在最后一条终止) > (location 部分起始路径) > (/)

3. 例子2:配置"try_files $uri $uri/index.html"

关键配置
absolute_redirect off; root /usr/share/nginx/html; index index.html; location = /a/index.html { default_type text/plain; return 200 hello; } location = /foo.html { default_type text/plain; return 200 bar; } location / { try_files $uri $uri/index.html /foo.html; #try_files $uri $uri/ /foo.html; }
  • a/index.html不存在,访问https://t.e1cent.top:8081/a 或者https://t.e1cent.top:8081/a/ 结果:都是直接返回bar 猜想:请求到了“try_files $uri $uri/index.html”之后,由于$uri/index.html不存在,匹配最后的“/foo.html”
  • 其他匹配不上的请求 结果:返回bar

4. 看法

  • 上述可见“try_files $uri $uri/index.html”比“try_files $uri $uri/”更精准一点,特别是涉及不带斜杠的重定向的场景。
  • 但是,实验环境很简单,假定是下面这种规则
location /path { root /home/html; index index.html; try_files $uri $uri/index.html 404; }
而HTML的js等文件路径类似public/js/0.js,可能会导致浏览器请求https://XXX/path(无反斜杠) 返回200,但引用的js文件请求变成https://XXX/public/js/0.js ,而只有https://XXX/path/public/js/0.js 才能请求到文件,这样会出现大量找不到文件的报错,HTML引用的路径是绝对路径或者带path的路径(/path/public/js/0.js)不会出现这种情况。而“try_files $uri $uri/”规则可以301重定向,让浏览器正确访问https://XXX/path/public/js/0.js ,故 “try_files $uri $uri/”更具普适性。

5. 验证4的猜想

5.1. 使用“try_files $uri $uri/ /foo.html;”

#关键配置 absolute_redirect off; access_log /var/log/nginx/access.log access; error_log /var/log/nginx/error.log error; root /usr/share/nginx/html; index index.html; location = /foo.html { default_type text/plain; return 202 bar; } location / { #try_files $uri $uri/index.html /foo.html; try_files $uri $uri/ /foo.html; }
#path文件路径,省略部分文件 tree html/path/ html/path/ ├── favicon.ico ├── index.html └── public ├── fonts ├── img └── js ├── app.js └── chunk-vendors.js
index.html引用文件为最简相对路径,如 <link href="public/js/0.js"
#开启日志追踪 docker logs -f -t --since="2022-05-27T02:42:00" --tail=20 mynginx|egrep -v 'fonts|img|[0-9].js'
  • 浏览器请求https://t.e1cent.top:8081/path日志看到path 301变成path/, index引用的文件相对路径全部带上path/:
    • notion image
  • 浏览器请求https://t.e1cent.top:8081/path/ 日志看到path/ 直接200 OK, index引用的文件相对路径全部带上path/:
    • notion image

5.2. 使用“try_files $uri $uri/index.html /foo.html;”

#关键配置 absolute_redirect off; access_log /var/log/nginx/access.log access; error_log /var/log/nginx/error.log error; root /usr/share/nginx/html; index index.html; location = /foo.html { default_type text/plain; return 202 bar; } location / { try_files $uri $uri/index.html /foo.html; #try_files $uri $uri/ /foo.html; }
修改index的“favicon.ico”相对路径为“path/favicon.ico”,其他静态文件路径不变,其他配置不变
  • 浏览器请求https://t.e1cent.top:8081/path 只有path是200,说明浏览器认为path就是index文件(虽然nginx反馈给它的是path/index.html),故静态文件应该和“index”同级别(index的静态文件都是相对路径,和index一个级别),比如浏览器要请求index引用的/path/favicon.ico,那就请求https://t.e1cent.top:8081/path/favicon.ico ,而/public/js/app.js的请求变成了https://t.e1cent.top:8081/public/js/app.js ,匹配了最后的重定向 /foo.html,拿到202状态码
    • notion image
  • 浏览器请求 https://t.e1cent.top:8081/path/ path/ 请求的是path/index.html,和未修改的静态文件都是200,因为浏览器认为静态文件和“index”同级别,都在path/下面,而“GET /path/path/favicon.ico”找不到变成202
    • notion image

5.3. 此部分的总结

  • 重定向和index有很大关系,故不要因为按照网上配置后请求失败就只觉得配置无用,或者配置错误
  • 浏览器的行为和nginx内部行为挺迷,不带反斜杠的请求+$uri/index.html 让浏览器认为index就在根目录(why?)
  • 生产环境的index引用尽量用全一点的相对路径,不要缩写,最好用绝对路径,如果用到了cdn,通常这部分静态文件全是绝对路径,如https://alicdn/cdnpath/favicon.ico 。毕竟“try_files $uri $uri/”可能导致多出301,浪费时间。
引申:那root换成alias不就让浏览器更懵逼了?

6. alias验证

6.1. 使用“try_files $uri $uri/index.html /foo.html;”

absolute_redirect off; access_log /var/log/nginx/access.log access; error_log /var/log/nginx/error.log error; root /usr/share/nginx/html; index index.html; location = /foo.html { default_type text/plain; return 202 bar; } location /pathnew { alias /usr/share/nginx/html/new; index index.html; try_files $uri $uri/index.html /foo.html; #try_files $uri $uri/ /foo.html; } location / { try_files $uri $uri/index.html /foo.html; #try_files $uri $uri/ /foo.html; }
#path文件路径,省略部分文件 tree html/new/ html/new/ ├── favicon.ico ├── index.html └── public ├── fonts ├── img └── js ├── app.js └── chunk-vendors.js
  • 浏览器请求https://t.e1cent.top:8081/pathnew 由于请求pathnew返回index文件,而静态文件路径和index同一级,那浏览器要请求index引用的path/favicon.ico,那就请求https://t.e1cent.top:8081/path/favicon.ico ,结果匹配到“location /”规则,而此规则对应根目录是root定义的root /usr/share/nginx/html,下面的path/favicon.ico存在,所以返回200。其他静态文件匹配“try_files $uri $uri/index.html /foo.html;”,但是前两个都没找到,最后重定向/foo.html ,返回202
    • notion image
  • 浏览器请求https://t.e1cent.top:8081/pathnew/ 按上面的逻辑,浏览器发现/usr/share/nginx/html/new/index.html是index文件,故静态文件继续匹配“location /pathnew”,在/usr/share/nginx/html/new/查找。
    • notion image
  • 上面两种情景是“$uri/index.html”还是“alias /usr/share/nginx/html/new;”的指引呢?下面继续验证
location = /foo.html { default_type text/plain; return 202 bar; } location = /new.html { //新增一个重定向 default_type text/plain; return 203 bye; } location /pathnew { alias /usr/share/nginx/html/new; index index.html; try_files $uri $uri/index.html /new.html; #try_files $uri $uri/ /new.html; } location /path3rd { //新增一个路由规则,但是/usr/share/nginx/html/c实际不存在 alias /usr/share/nginx/html/c; index index.html; try_files $uri $uri/index.html /new.html; #try_files $uri $uri/ /new.html; }
验证此部分时,run一个新的nginx,配置了端口转发:443->8080
3.1 浏览器请求https://t.e1cent.top/pathnewa
pathnewa目录不存在,但是pathnew目录存在,可请求匹配到“location /pathnew”后并没有去alias指定的/usr/share/nginx/html/new寻找index,看着像分别查找了pathnewa、pathnewa/index.html但是未找到,最后匹配到/new.html 重定向返回203
notion image
开启error_log /var/log/nginx/error.log debug; 删除最后的重定向,让错误打印出来
#浏览器请求https://t.e1cent.top/pathnewccc #日志显示寻找了/usr/share/nginx/htmlccc/index.html,哪条规则改写了/usr/share/nginx/htmlccc? #修改root目录为/usr/share/nginx,请求就变成了/usr/share/nginxccc/index.html,即匹配了“location /”规则 [error] 53#53: *17 open() "/usr/share/nginx/htmlccc/index.html" failed (2: No such file or directory), client: 172.17.0.1, server: t.e1cent.top, request: "GET /pathnewccc HTTP/1.0", host: "t.e1cent.top"
3.2 浏览器请求https://t.e1cent.top/pathnewa/
pathnewa目录不存在,但是pathnew目录存在,可请求匹配到“location /pathnew”后并没有去alias指定的/usr/share/nginx/html/new寻找index,最后匹配到/new.html 重定向返回203
notion image
3.3 浏览器请求https://t.e1cent.top/path3rd
/usr/share/nginx/html/c目录不存在,请求匹配到/new.html 重定向返回203
notion image
3.4 浏览器请求https://t.e1cent.top/path3rd/
/usr/share/nginx/html/c目录不存在,请求匹配到/new.html 重定向返回203
notion image

6.2 使用“try_files $uri $uri/ /foo.html;”

absolute_redirect off; access_log /var/log/nginx/access.log access; error_log /var/log/nginx/error.log error; root /usr/share/nginx/html; index index.html; location = /foo.html { default_type text/plain; return 202 bar; } location /pathnew { alias /usr/share/nginx/html/new; index index.html; #try_files $uri $uri/index.html /foo.html; try_files $uri $uri/ /foo.html; } location / { try_files $uri $uri/index.html /foo.html; #try_files $uri $uri/ /foo.html; }
  • 浏览器请求https://t.e1cent.top:8081/pathnew index先301再200,静态文件相对路径未修改的全部200,而“GET /pathnew/path/favicon.ico”出现202
    • notion image
  • 浏览器请求https://t.e1cent.top:8081/pathnew/ index直接200,静态文件相对路径未修改的全部200,而“GET /pathnew/path/favicon.ico”出现202
    • notion image

7. 最后

  • 场景:SPA前后分离项目,nginx只用来指示静态文件,不做api转发 建议配置:和前端约定,每个项目index静态文件都带路由,即引用路径为path/public/1.js而不是public/1.js,生产环境更可能是cdn的绝对路径。nginx用不同端口号区分不同的域名或者环境,nginx之前用代理(如slb)统一处理443加密请求
#dev环境 server{ listen 81; root /usr/share/nginx/dev; index index.html; ...... location / { try_files $uri $uri/index.html @redirect; } ...... }
#dev项目目录 /usr/share/nginx/dev ├── path1 ├── favicon.ico ├── index.html └── public ├── fonts ├── img └── js ├── app.js └── chunk-vendors.js ├── path2 └── path3
 
技术
  • 中间件
  • nginx 获取 XFF 的第一个 ip 和 “真正的” realip python2和python3共存使用 virtualenvwrapper-win管理环境
    目录