项目一直使用Nginx作为流量网关,由于网络调整,用户网络跟Nginx服务器网络无法直连,采用了端口映射的方式(服务实际地址为10.0.24.204/wywy,映射后为10.0.8.2:8000/wywy),导致nginx配置出现问题。

问题1: 根目录重定向错误

原NG配置中,设置了根目录 / 重定向到 /wywy。实现浏览器访问http://10.0.24.204时,会自动跳转到http://10.0.24.204/wywy/。原始配置如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
server {
    listen       80;
    server_name  localhost;

    location / {
        rewrite ^/$ /wywy/ permanent;
    }
      
    location ~ /wywy {
    add_header Cache-Control "no-cache, no-store";
    root   /data/application;
    index  index.html index.htm;
    }
}

但是当使用端口映射后,浏览器访问http://10.0.8.2:8000,并没有跳转到http://10.0.8.2:8000/wywy。NG返回301后,header里的Locationhttp://10.0.8.2/wywy,注意端口消失了。

问题2: 资源目录重定向错误

不知你注意到没有,当浏览器访问http://10.0.24.204/wywy地址后,会自动在后面加上斜杠/,即变成http://10.0.24.204/wywy/,打开开发者工具(F12),在network标签里,会看到一个301和一个200,两次请求返回:

image-20221129172911675

这是NG主动设置301 Moved Permanently的结果。原理是当用户输入了一个url地址,NG没有找到URL最后部分的资源,并且发现最后部分是一个文件目录(比如wywy是我的目录,里面只有一个index.html),则本次访问的状态码会被设置成301,并在Response header里增加一个Location项,下面会讲这个Location如何取值,这里默认返回的就是http://10.0.24.204/wywy/,增加了一个斜杠/。这时按照配置规则,在wywy/目录下查找index.html资源,于是返回了网页信息。

同样的,当使用端口映射以后,访问新地址http://10.0.8.2:8000/wywy时,会被301重定向到http://10.0.8.2/wywy/,增加斜杠/但端口又消失了。

Nginx重定向配置

nginx默认有三个重定向的参数可以配置:

  • absolute_redirect:On(开启)时使用绝对URL,并开启下面两个配置参数,共同影响301重定向时Location的取值。Off(关闭)时,使用相对URL作为Location取值。默认开启
  • server_name_in_redirect:只在absolute_redirect开启时生效。On(开启)时,使用NG配置的server_name。Off(关闭)时,使用用户请求输入URL的服务器部分。默认关闭
  • port_in_redirect:只在absolute_redirect开启时生效。On(开启)时,加入本地监听的的端口号,但是除443、80外。Off(关闭)时,不加端口号。默认开启

所以,在默认情况下,如果你的NG配置监听的是80或443这两个默认端口,则使用的是绝对URL+用户输入服务器host+无端口号。所以就出现了上面的端口消失情况。

解决方案

先重复下需要达到效果,使用端口映射10.0.8.2:8000 –> 10.0.24.204:80的情况下:

  1. 访问根目录http://10.0.8.2:8000可以自动跳转到http://10.0.8.2:8000/wywy/
  2. 访问资源目录没有带最后的斜杠http://10.0.8.2:8000/wywy,可以自动跳转到http://10.0.8.2:8000/wywy/
  3. 不影响原本ip访问

了解完原理后,解决方案就简单了:

  1. 直接在server中设置absolute_redirect参数为Off,这样NG就直接使用相对URL,即保留请求时的host和port,只修改uri部分,最终配置如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    server {
        listen       80;
        server_name  localhost;
        absolute_redirect off;
    
        location / {
            rewrite ^/$ /wywy/ permanent;
        }
    
        location ~ /wywy/ {
            add_header Cache-Control "no-cache, no-store";
            root   /data/application;
            index  index.html index.htm;
        }
    }
    

    该参数是一个全局参数,可能会影响其他规则的运作,造成不可知的问题影响其他服务。配置的时候应该考虑这个因素。

    所以当你NG只提供你的一个服务时,可以放心采用1方案,快捷简单。

  2. 使用rewrite重写重定向路径,相当于自己定义重定向的规则,不受absolute_redirect配置影响。为了满足第二点需求,需要增加一个location项,最终配置如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    server {
        listen       80;
        server_name  localhost;
        #absolute_redirect on; # 默认为on
    
        location / {
            if (-d $request_filename) {
                rewrite ^/$ ${scheme}://${http_host}/wywy/ permanent;
            }
        }
    
        location /wywy {
            rewrite ^(.*)$ $scheme://${http_host}${uri}/ permanent;
        }
        location ~ /wywy/ {
            add_header Cache-Control "no-cache, no-store";
            root   /data/application;
            index  index.html index.htm;
        }
    }
    

    使用rewrite,手动指定用户访问根目录/以及/wywy时,要如何跳转到/wywy/

  3. (新增)无意中发现另外一个现象,网上找了圈资料,没有找到具体解释。使用rewrite重写,当跳转地址是以/开头时,nginx会默认使用绝对路径,并且受到absolute_redirect以及另外两个子参数的影响。此时rewrite时,会去替换URI。当把开头的斜杠/去掉时,会默认使用相对路径,并且只会替换请求地址URI的最后一部分。下面讲rewrite会详细解释。

    所以实现上述需求跳转时,只要写明地址并不要/开头即可。具体配置如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    server {
        listen       80;
        server_name  localhost;
        #absolute_redirect on; # 默认为on
    
        location / {
            if (-d $request_filename) {
                rewrite ^/$ wywy/ permanent;
            }
        }
    
        location /wywy {
            rewrite ^(.*)$ wywy/ permanent;
        }
        location ~ /wywy/ {
            add_header Cache-Control "no-cache, no-store";
            root   /data/application;
            index  index.html index.htm;
        }
    }
    

Rewrite使用以及参数解释

​ rewrite的功能就是,结合正则表达式,实现url重写重定向。他可以使用NG的全局变量和自定义变量。关键字位置为:server{}、location{}、if{}

语法: rewrite [flag];

regex: 正则表达式

replacement:跳转后地址。可以写完整地址如http://www.baidu.com/somethings,也可以省略前面的协议+host+port部分,如/wywy/或者wywy/。此时当以/开头时,会默认按照规则补全协议、host、port(即绝对路径,并依赖上述absolute_redirect参数的控制),当不以/开头时,会以相对路径进行跳转,注意这里相对的是当前资源的路径,如你访问http://10.0.24.204/abc/def/xyz时,跳转路径会是http://10.0.24.204/abc/def/wywy/

flag:支持的flag标记,共4种last,break,redirect,permanent

四种flag标记

last:本条规则匹配完成后,继续向下匹配新的location URI规则,一般用在 server 和 if 中

break:本条规则匹配完成即终止,不再匹配后面的任何规则,一般使用在 location 中

redirect:返回302临时重定向,浏览器地址会显示跳转后的URL地址

permanent:返回301永久重定向,浏览器地址栏会显示跳转后的URL地址。

NGINX部分保留变量

$scheme: 协议部分,即http、tcp这些

$http_host: 服务器地址,包含端口号

$uri: 去掉协议、地址、端口后的资源相对路径,注意时自带最左边斜杠的。即/wywy

$request_filename: 请求的资源名称,即访问路径最后的部分。如http://10.0.8.2:8000/abc/wywy则表示的是wywy

正则注意项

^表示字符串开头

$表示字符串结尾

[^abc]如果在方括号里面则表示否定、非的意思。

案例

做一个实验,在nginx的根目录里做rewrite

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
server {
    listen       80;
    server_name  localhost;
    location / {
        rewrite ^(.*)$ http://www.baidu.com permanent; # 1
        rewrite ^(.*)$ ${scheme}://${http_host}${uri}wywy/ permanent; # 2
        rewrite ^(.*)$ /wywy/ permanent; # 3
        rewrite ^(.*)$ wywy/ permanent; # 4
        rewrite ^/(.*)([^/]*)$ /wywy/ permanent; # 5
        rewrite ^/(.*)([^/]*)$ wywy/ permanent; # 6	
    }
}
  1. 正则部分表示匹配所有,跳转地址是完整的百度首页。

    访问http://10.0.24.204/,结果为:Location: http://www.baidu.com,浏览器跳转到了百度。

  2. 正则部分表示匹配所有,跳转地址手动指定了用户访问的协议、地址端口、uri。

    访问http://10.0.24.204/时,结果为:Location: http://10.0.24.204/wywy/

    访问http://10.0.24.204/abc时,结果为:Location: http://10.0.24.204/abc/wywy/

    访问http://10.0.8.2:8000时,结果为:Location: http://10.0.8.2:8000/wywy/

    访问http://10.0.8.2:8000/abc时,结果为:Location: http://10.0.8.2:8000/abcwywy/

  3. 正则部分表示匹配所有,跳转地址为斜杠开头的资源/wywy/

    访问http://10.0.24.204/时,结果为:Location: http://10.0.24.204/wywy/

    访问http://10.0.24.204/abc/xyz时,结果为:Location: http://10.0.24.204/wywy/⚠️注意abc/xyz被覆盖

    访问http://10.0.8.2:8000时,结果为:Location: http://10.0.8.2/wywy/ ⚠️注意端口号消失

    访问http://10.0.8.2:8000/abc/xyz时,结果为:Location: http://10.0.8.2/wywy/⚠️注意端口号消失,而且abc也被覆盖

    如上述rewrite规则,当省略协议、地址端口,资源以斜杠开头时,nginx会根据默认规则默认帮你在斜杠/之前填写上协议、地址和端口。你的请求URI部分会被忽略掉。

    如果你没有自定义重定向配置,那规则就是absolute_redirect on 使用绝对URL,server_name_in_redirect off 使用用户请求输入URL的服务器部分,port_in_redirect on 加入本地监听的的端口号,但是除443、80外。由于本地监听了80,所以此处不会增加端口号,所以端口号消失了。

    所以就变成了你请求时的协议(http)+ 你请求时的host(10.0.24.204或者10.0.8.2)+你定义的转跳地址(/wywy/)

  4. 正则部分表示匹配所有,跳转地址为非斜杠开头的资源wywy/

    访问http://10.0.24.204/时,结果为:Location: wywy/

    访问http://10.0.24.204/abc时,结果为:Location: wywy/,浏览器跳转地址为http://10.0.24.204/abc/wywy/

    访问http://10.0.8.2:8000时,结果为:Location: wywy/, 浏览器跳转地址为http://10.0.8.2:8000/wywy/

    访问http://10.0.8.2:8000/abc/xyz时,结果为:Location: wywy/, 浏览器跳转地址为http://10.0.8.2:8000/abc/wywy/

    ⚠️注意这里相对路径,只针对你访问资源的级别,如最后一个例子,仅仅xyz被替换成wywy/,如果你访问abc/xyz/的话,最终你将得到abc/xyz/wywy/

  5. 正则部分表示,匹配斜杠开头并不以斜杠结尾,跳转地址为绝对路径的/wywy/

    效果同3

  6. 正则部分表示,匹配斜杠开头并不以斜杠结尾,跳转地址为相对路径的wywy/

    效果同4