Skip to content

nginx 301 问题排查解析

起因

最近在银联云项目双域名改造的需求上,遇到了一件怪事。
双域名改造,顾名思义,就是将页面改造成双域名 😅,当然为了节省资源,两个域名访问的都是同一套环境,同一台机器。
改造的内容也就是简单的页面跳转,把原来写死的跳转链接,改造成根据浏览器环境来判断。

js
// www.hct.com or login.hct.cn or blog.hct.com.cn
const s = window.location.hostname.split("hct")[1];
// hct
const domain = "hct" + s;

window.open("www." + domain);

在改造完开始测试时,就碰到了开头提到的怪事,在测试 cn 域名时,有时进入页面之后,后端接口会报用户未登录的错误。

排查

打开控制台,发现请求的后端接口域名变成了 com,由于我们数栈是通过 iframe 嵌套在银联云页面中的, 一开始手动输入数栈网址排查,页面都是正常请求了 cn 的后端接口。
查看银联云 iframe 的 src,配置的也是 cn 的域名,但当我将 src 复制出来粘贴到地址栏之后,出现了这个现象。

301

可以看到第一个请求,给出了一个 301 的重定向状态码,并且响应头给出了一个新的 location,让页面从跳转到 cn 跳转到了 com,并给结尾带上了一个 /
在查看我们生产环境和我们的 devops 环境后,发现我们所有的产品,也都会在结尾带上一个 /

301

301

/ 的意义

首先肯定是要明白,结尾带 / 和不带 / 的 uri 分别是什么意思。

以终端的一条命令行为例:

  • /Users/hct/Desktop/index.html
    不带 /,意味着我们要查看或执行 index.html。至于这个 index.html,也不排除是个文件夹的可能 💩。
  • /Users/hct/Desktop/index.html/
    带上 /,意味着我们要访问 index.html 这个文件夹 😅,对 index.html 这个文件夹里面的东西进行一些操作。

在服务器内部或者 ftp 协议这种比较严格的远程服务上,对于 uri 的要求是非常严格的,写错路径的结尾就可能会拒绝访问,但在一些现代的 http 协议 web 服务器中,访问的 uri 就相对的随意但是复杂了。
在现代 web 中,地址栏输入的链接,最后会展示什么,受到太多的东西控制了。

protocol://hostname[:port]/path/[:parameters][?query]#fragment
在以前,parameters 不填写完整,返回的就是 404,现在服务器可以自动帮你返回 index.html。

hash 路由的 spa 项目,可以通过 fragment 来控制页面。
在以前,一个 parameters就对应了一个 html 文件,现在 history 路由的 spa 项目,配合 web 服务器,能够做到拦截所有的 parameters,让一个 html 文件来控制所有 parameters 的变化。

为什么会 301?

导致 301 的原因,其实就是 try_files 这个配置项。在 nginx 的 location 中(不确定是那个版本开始的),有这样一条默认的 try_files 配置:
try_files $uri $uri/ =404;
在这条配置中,如果 $uri 访问的不是正确的文件,nginx 会在服务器内部先尝试访问 $uri/,搭配 nginx 自带的默认配置 index index.html index.htm;,nginx 会去匹配 $uri/index.html,如果命中,nginx 就会向浏览器返回 301,让浏览器重定向到 $uri/,正常访问页面。

301

当 nginx 的 try_files 依次匹配到最后一项时,nginx 将不会返回 301,而是直接内部重定向到最后一项,将结果返回给 $uri/ 的请求。

301

history 模式

protocol://hostname[:port]/path/[:parameters][?query]#fragment
众所周知,spa 应用的 hash 模式,是依靠浏览器的 # 锚点实现的,在服务器返回 html 文件后,spa 应用通过拦截 fragment 来读写前端路由目前的状态。
history 模式,则是由浏览器直接拦截 pathparameters 来实现的。而浏览器的拦截,只能在获取到 html 之后,加载了必要的 js 才能开始生效,这就意味了第一次请求的时候,网址上的 path 很可能都是 404。
这就需要后端 web 服务器进行对应的配置,让所有匹配不到的路径,都返回 spa 应用的 html 入口。
try_files $uri $uri/ /index.html;

上层代理

明白了 301 的原理,现在就要回到一开始的问题,为什么 301 被重定向到了另一个域名。

301

经过一番排查,可以得出我们的 nginx 配置并没有什么问题,只能推测是上层的 nginx 的配置导致了问题。
由于没有查看上层配置的权限,只能自己简单搭建一个来模拟一下两层 nginx 和 多个域名指向同一 ip 时的配置

conf
server {
  listen 80;
  server_name localhost 192.168.97.247;

  location / {
    rewrite / /insider permanent;
  }

  location /insider {
    alias /Users/hct/Desktop/personal/insider;
  }
}

server {
  listen 80;
  server_name local.devops.dtstack.com local.devops.dtstack.cn;

  location / {
    proxy_pass http://localhost:8080;
  }
}
# 用另一端口模拟别的机器
server {
  listen 8080;
  server_name_in_redirect off;
  server_name foo;

  location / {
    rewrite / /nested permanent;
  }

  location /nested {
    alias /Users/hct/Desktop/personal/nested;
    index index.html index.htm;
    autoindex on;
  }
}

草稿

root alias 区别

root 会带上 location 匹配到的 url,alias 不会

absolute_redirect server_name_in_redirect port_in_redirect

同 ip 不同域名