Fork me on GitHub
Fork me on GitHub

Nginx如何处理一个请求

今天在配置Nginx多个虚拟主机的时候,发现一个问题,访问一个虚拟主机里的资源,结果却访问到了另外一个虚拟主机中的资源,赶紧去看看了官方文档,补补知识,在此记录一下Nginx是如何选定由哪个虚拟主机去处理请求的。

问题及现象

由于公司应用较多,所以单独选择了一个地方存放应用的Nginx配置文件,在Nginx主配置文件中引入这个路径的配置文件就可以了。
在配置某个应用程序(rsfw_origin.conf)需要临时支持下https的时候,要求关闭http,只允许https。于是复制该应用配置文件为一个名字(rsfw_https.conf),修改新配置文件里的一些与原有配置文件冲突的命名,比如upstream_server的名字等。并且关闭了http,只开启了https。两个配置文件内容大体如下:
rsfw_https.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server {
listen 443;
server_name rsfw.wisedu.com;
ssl on;
#证书和私钥
ssl_certificate /opt/ssl/rsfw.crt;
ssl_certificate_key /opt/ssl/rsfw.key;
access_log logs/gemini.access.log gemini;
location /test {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect http:// https://;
proxy_pass http://rsfw_server;
}
...

rsfw_origin.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server {
listen 80;
server_name testdt.wisedu.com;
access_log logs/rsfw_adapter.access.log rsfw_origin;
location / {
root html;
index index.html index.htm;
}
location /test {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://rsfw_server_origin;
}
...

然后浏览器访问:https://rsfw.wisedu.com/test/sys/emaphome/portal/index.do
输入用户名密码登录认证,没有问题。
接着浏览器访问:http://rsfw.wisedu.com/test/sys/emaphome/portal/index.do
我原以为应该是访问不了,结果竟然还跳到了登录页面。两个虚拟主机,我访问其中一个虚拟主机下的资源,怎么会跳到另外一个虚拟主机下寻找资源呢?(两个虚拟主机下都有location /test)
查看nginx的error.log,发现是server: testdt.wisedu.com这台虚拟主机响应了请求(看下面的server:testdt.wisedu.com):

1
2019/03/06 14:52:29 [warn] 3754#0: *1624 an upstream response is buffered to a temporary file /usr/local/openresty/nginx/proxy_temp/2/09/0000000092 while reading upstream, client: 172.20.5.15, server: testdt.wisedu.com, request: "GET /test/sys/emapfunauth/pages/funauth-login/images/login_bg.jpg HTTP/1.1", upstream: "http://172.16.7.78:8003/test/sys/emapfunauth/pages/funauth-login/images/login_bg.jpg", host: "rsfw.wisedu.com", referrer: "http://rsfw.wisedu.com/test/sys/emapfunauth/pages/funauth-login/funauth-login.css"

由此可见,Nginx对于使用哪个虚拟主机来处理用户请求应该有自己的一套规则,于是赶紧去官方文档找找资料。

原因及解决

官方文档:http://nginx.org/en/docs/http/request_processing.html
中文文档:https://tengine.taobao.org/nginx_docs/cn/docs/http/request_processing.html#how_to_prevent_undefined_server_names

总结一下,就是nginx会检测请求中的port是否匹配某个server配置块中listen指令,接着nginx继续测试请求的Host头是否匹配这个server块中的某个server_name的值。
如果主机名没有找到,nginx将把这个请求交给默认虚拟主机处理。第一个被列出的虚拟主机即nginx的默认虚拟主机——这是nginx的默认行为。而且,可以显式地设置某个主机为默认虚拟主机,即在”listen”指令中设置”default_server”参数:

1
2
3
4
5
server {
listen 80 default_server;
server_name test.wisedu.com;
...
}

在上面的例子中,由于rsfw_https.conf中的虚拟主机监听在了443端口,所以当我浏览器访问http://rsfw.wisedu.com/test/sys/emaphome/portal/index.do,不会被rsfw_https.conf中的虚拟主机rsfw.wisedu.com匹配到,于是交到了监听80端口的默认虚拟主机testdt.wisedu.com。

接着做个实验,重新写一个配置文件,同时手动指定默认虚拟主机。新建一个配置文件wisedu_fe.conf,在其中加上default_server配置,如下:

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 80 default_server;
server_name ressignal.wisedu.com;
access_log logs/res.access.log res;
location / {
root /opt/wisedu_fe/ver;
index index.html index.htm;
add_header Access-Control-Allow-Origin *;
}
...

加了default_server,这台新的虚拟主机就成为了默认虚拟主机。重载Nginx配置文件。
浏览器输入http://rsfw.wisedu.com/test/sys/emaphome/portal/index.do
结果:浏览器显示404,而不在登录界面了。
error.log日志:

1
2019/03/06 15:28:53 [error] 4208#0: *4204 open() "/opt/wisedu_fe/ver/test/sys/emaphome/portal/index.do" failed (2: No such file or directory), client: 172.20.5.15, server: ressignal.wisedu.com, request: "GET /test/sys/emaphome/portal/index.do HTTP/1.1", host: "rsfw.wisedu.com"

相应的server也变为了server: ressignal.wisedu.com