今天遇到一个nginx重定向的问题,应用通过重定向访问nginx的80端口的Html静态服务,在重定向时导致端口丢失现象,通过网络查找资料记之如下。

nginx有的时候并不像Apache那样智能,对于redirect location的处理尤为惨淡,几乎只能用户手工处理非标准端口的问题。

比如因为种种原因,nginx并不能监听在80端口,或者外部通过NAT方式将请求丢给nginx,外部地址并不是标准http(s)端口,此时nginx并不能美好的处理这些重定向。发生重定向的时候会丢失端口。

比如以下两种配置方案:

#反向代理
listen 81 default_server;
set $TOMCAT_HOME /var/lib/tomcat7;

location / {
    root $TOMCAT_HOME/webapps/ROOT;
    proxy_pass http://127.0.0.1:8080/;
    proxy_set_header Host             $host;
    proxy_set_header X-Real-IP        $remote_addr;  
    proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;
}
# 访问/data的时候,浏览器发出请求`http://xxx.com/data`
# 未查询到/data文件,url被重定向到`http://xxx.com/data/`目录
listen 81 default_server;

location /data {
    root /var/data;
}

浏览器请求的时候会发现只要发生重定向,端口号就会丢失,导致浏览器访问到错误的端口。

分别对这两种情况给出解决方案。

反向代理

这个很容易搞定,那一堆 proxy_set_header不知道何时在网上流传开来的,几乎国内外文档无一例外都是这个鸟样子。

后来我发现gitlab-ce这个用非标准端口访问是没有问题的,我看了一下 gitlab -ce

proxy_set_header Host   $http_host;

我又发现nginx软件包释放出的配置文件,发现里面其实是带有一个参考文件/etc/nginx/proxy_params

proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

这里面写的也是  proxy_set_header Host $http_host;,于是乎直接include,搞定

location / {
    root $TOMCAT_HOME/webapps/ROOT;
    proxy_pass http://127.0.0.1:8080/;
    include proxy_params;
}

没这个文件就把这些内容手工敲到location配置段下。

再细看官方文档,其实也提及了:

An unchanged “Host” request header field can be passed like this:

  proxy_set_header Host       $http_host;

访问目录没带 / 

这个比较棘手,比如 $document_root存在 data/index.html文件,但是访问的时候使用的是/data最后没加/去表示其访问的是一个目录,

服务器此时会进行301 Moved Permanently永久重定向处理,

但是比较扯的地方在于,如果nginx监听的是非标准端口,这个301返回的 Location 没有端口号,导致浏览器请求出错。

用curl可以很明显的看到这一点:

$ curl -v mydomain:81/data
* Hostname was NOT found in DNS cache
*   Trying *.*.*.*...
* Connected to mydomain (*.*.*.*) port 81 (#0)
> GET /test HTTP/1.1
> User-Agent: curl/7.35.0
> Host: mydomain:81
> Accept: */*
> 
< HTTP/1.1 301 Moved Permanently
* Server nginx is not blacklisted
< Server: nginx
< Date: Wed, 18 Nov 2015 07:39:03 GMT
< Content-Type: text/html
< Content-Length: 178
< Connection: keep-alive
< Location: http://mydomain/data/
< 
<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx</center>
</body>
</html>
* Connection #0 to host mydomain left intact

可以很明显的看到 Location 没有端口号了,这个重定向又和反向代理不一样。于是遍寻google,最终在stackoverflow的问答中找到了解决方案:

if (-d $request_filename) {
   rewrite [^/]$ $scheme://$http_host$uri/ permanent;
}

通过配置对URL重写的形式带上端口号,问题解决。