对于nginx,大家已经如雷贯耳。但大多数人像我一样只是停留在配置使用阶段。对于出现问题不知道如何定位问题,只能是百度,遵循前人的经验。或者打开官方网站查找。昨天我们上线就遇到了一个nginx配置不当引发301问题
问题描述:新系统上线完后,发现通过域名访问时而好用(mgmt.jerry.com/demo/)时而不好用(mgmt.jerry.com/demo).仔细观察请求地址发现不好用的时候项目名后面没有斜杠“/”。这是为什么呢?另外发现请求不带斜杠nginx默认301永久重定向到mgmt.jerry.com:8080/demo/ (nginx配置的监听端口是8080)端口号由80变成了8080
以下是我们nginx.conf的部分配置
location ~ ^/(demo)/ {
proxy_pass http://127.0.0.1:8080/$1/index.html;
}
location ~ ^/(demo1)/ {
proxy_pass http://127.0.0.1:8080/$1/index.html;
}
location ~ ^/(demo2)/ {
proxy_pass http://127.0.0.1:8080/$1/index.html;
}
location = / {
proxy_pass http://127.0.0.1:8080/demo;
}
配置文件的意图很明显:不同的项目名过来命中不同的index页面,默认只访问域名代理到demo的index页面。
~ ^ 匹配输入字符串开始的位置,location的正则表达式中使用了目录/(demo)/,当在浏览器输入mgmt.jerry.com/demo是无法匹配上的。而mgmt.jerry.com/demo/是可以匹配上的,匹配上就可以正常访问了demo/index.html了。这就解释了有的时候好使,有时候不好使的问题了,立马修改配置为
location ~ ^/(demo) {
proxy_pass http://127.0.0.1:8080/$1/index.html;
}
location ~ ^/(demo1) {
proxy_pass http://127.0.0.1:8080/$1/index.html;
}
location ~ ^/(demo2) {
proxy_pass http://127.0.0.1:8080/$1/index.html;
}
location = / {
proxy_pass http://127.0.0.1:8080/demo;
}
这样不管带不带斜杠都能正常匹配,心里美滋滋。不过仔细看配置就会发现还会有另一个问题:
假如我请求的地址是mgmt.jerry.com/demoabc,那照样也可以匹配到location ~ ^/(demo) ,并且正常访问。这肯定是不能容忍的。那有没有一个表达式机能满足业务需求又能将三个location合并成一个呢?
当然有,那就要用\w(匹配包括下划线的任何单词字符。等价于’[A-Za-z0-9_]’)了。我们的访问路径中规定只有字母、数字、下划线、斜杠。那使用\w就可以轻松解决了。后续即使有新项目接入,也可以无需修改表达式了。完整配置如下:
location ~ ^/(\w+)/? {
proxy_pass http://127.0.0.1:8080/$1/index.html;
}
location = / {
proxy_pass http://127.0.0.1:8080/demo;
}
当你访问的项目名字不存在的时候,直接会返回404(前端会跳转到错误页面)
接下来就是访问域名或根目录了(mgmt.jerry.com),发现通过以上方式还是不OK的,没有达到想要的效果。跟前端小伙伴沟通发现前端本次上线删除了重定向,需要nginx协助实现重定向,定位了问题就好解决了。改成如下配置
location ~ ^/(\w+)/? {
proxy_pass http://127.0.0.1:8080/$1/index.html;
}
location = / {
rewrite / /demo/ redirect;
}
发现还是不ok,什么问题呢?跟一开始说的问题是一样的。域名http默认端口是80,而我们重定向后的端口是8080,通过域名:8080/demo/访问肯定是不OK的,那么该如何解决呢?
方案一:使用nginx的内置变量$host,即如下配置
location ~ ^/(\w+)/? {
proxy_pass http://127.0.0.1:8080/$1/index.html;
}
location = / {
rewrite / http://$host/demo/ redirect;
}
直接利用host重定向到默认不带端口号地址。
方案二:之所以重定向出现端口后变化,是跟nginx配置有关系的。参照http://nginx.org/en/docs/http/ngx_http_core_module.html#port_in_redirect
nginx配置中有一参数port_in_redirect,默认是on。配合server_name_in_redirect
Enables or disables the use of the primary server name, specified by the server_name directive, in absolute redirects issued by nginx. When the use of the primary server name is disabled, the name from the “Host” request header field is used. If this field is not present, the IP address of the server is used.
The use of a port in redirects is controlled by the port_in_redirect directive
简单理解就是server_name_in_redirect默认是打开的。如果是打开的,重定向的时候会使用nginx.conf中配置的server_name,并且配合port_in_redirect返回端口号。如果server_name_in_redirect是off,则使用request中的host作为定向的host。
知道了这一点后就可以修改如下配置了
location ~ ^/(\w+)/? {
proxy_pass http://127.0.0.1:8080/$1/index.html;
}
location = / {
server_name_in_redirect off;
port_in_redirect off;
rewrite / /demo/ redirect;
}
如果你只想重定向的端口不变,跳转地址使用server_name或者其他地址,可以采用如下配置:
location = / {
port_in_redirect off;
rewrite / http://yourIP/demo/ redirect;
}
接下来再说一下最开始说的301重定向后加了/问题,这个可以参照nginx说明
If a location is defined by a prefix string that ends with the slash character, and requests are processed by one of proxy_pass, fastcgi_pass, uwsgi_pass, scgi_pass, memcached_pass, or grpc_pass, then the special processing is performed. In response to a request with URI equal to this string, but without the trailing slash, a permanent redirect with the code 301 will be returned to the requested URI with the slash appended. If this is not desired, an exact match of the URI and location could be defined like this:
location /user/ {
proxy_pass http://user.example.com;
}
location = /user {
proxy_pass http://login.example.com;
}
大体意思是说:如果你只配置了带/的正则,并且使用了proxy_path等几个功能,那么当你不带/请求的时候,就会返回301,并且带上/,如果想避免这个问题,就要配置两个正则,一个带,一个不带。当然你也可以写正则表达式,将两种情况都匹配上解决哟。