本文是对 how nginx processes a request 官方文档 的翻译。
基于名称的虚拟主机
nginx 首先会决定使用哪个 server 去处理请求,下面是一个配置文件示例,所有的虚拟主机都监听在 80 端口上。
server {
listen 80;
server_name example.org www.example.org;
}
server {
listen 80;
server_name example.net www.example.net;
}
server {
listen 80;
server_name example.com www.example.com;
}
在此配置中,nginx 仅会检测 request header 中的 Host 字段,以此来决定将 request 发到哪个 server。如果没有匹配到任何 server_name 或者 request header 中没有 Host 字段,nginx 会将 request 发到此端口的 default server 上。
在此配置中,第一个 server 就是 default server —— nginx 的默认行为。当然,也可以通过 default_server 参数来指定哪个是 default server。
server {
listen 80 default_server;
server_name example.net www.example.net;
}
注意:default_server 是 listen 的参数,不是 server_name 的参数。
注意:default_server 在 0.8.21 版本及之后才可用,之前的版本使用 default。
如何不处理未指定主机名的请求
如果不允许处理 header 中没有 Host 字段的 request,那么可以这么定义 server:
server {
listen 80;
server_name "";
return 444;
}
server_name 被设置为空字符串,用来匹配 header 中没有 Host 字段的 request,然后返回 444 状态码(一个特殊的,nginx 的非标准状态码)以关闭连接。
0.8.48 及之后的版本已经是默认设置了,所以可以省略 server_name ""
。
基于名称和基于 IP 的混合虚拟主机
来看一个更复杂的配置,这些虚拟主机监听在不同的地址上。
server {
listen 192.168.1.1:80;
server_name example.org www.example.org;
}
server {
listen 192.168.1.1:80;
server_name example.net www.example.net;
}
server {
listen 192.168.1.2:80;
server_name example.com www.example.com;
}
在此配置中,nginx 首先会根据各 server 块中的 listen 指令,检测 request 的 ip:port。然后再根据和 request 的 ip:port 匹配 server 块中的 server_name 检测 request header 中的 Host 字段。 如果 server_name 没有匹配到,则将该 request 交给和 ip:port 匹配 default server 处理。
比如 192.168.1.1:80 接收到了对 www.example.com 的 request,但是和 192.168.1.1:80 匹配的两个 server 块的 server_name 都和 www.example.com 不匹配,因此该 request 被交给这两个 server 中的第一个 server 块处理(如果没有用 default_server 指定)。
就像之前所说,default_server 是 listen 指令的一个参数,因此不同的 ip:port 可以定义不同的 default_server。
server {
listen 192.168.1.1:80;
server_name example.org www.example.org;
}
server {
listen 192.168.1.1:80 default_server;
server_name example.net www.example.net;
}
server {
listen 192.168.1.2:80 default_server;
server_name example.com www.example.com;
}
一个简单的 PHP 站点配置
server {
listen 80;
server_name example.org www.example.org;
root /data/www;
location / {
index index.html index.php;
}
location ~* \.(gif|jpg|png)$ {
expires 30d;
}
location ~ \.php$ {
fastcgi_pass localhost:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
nginx 首先找到最具体的 前缀 location,无论在配置文件中的顺序如何。上述配置文件中的前缀 location 就只有 "/" 一个,且因为它能匹配任意的 request,所以被最后采用。
然后 nginx 按照在配置文件中的顺序检查 正则表达式 location,找到并使用第一个匹配的 location,且不再向下查找。
如果没有一个 正则表达式 location 匹配,那么就使用之前找到的最具体的 前缀 location。
注意:location 匹配的是 request 的 uri,不包括 query、hash 等。
现在来看一看在上述配置文件中,request 是怎么被处理的:
/logo.gif
:被"/"
和~* \.(gif|jpg|png)$
两个 location 匹配到,因此会被后者处理,根据root /data/www
指令,该 request 会被映射到/data/www/logo.gif
文件,并将该文件发送给客户端/index.php
:被"/"
和\.(php)$
两个 location 匹配到,因此会被后者处理,该 request 会被转发到 FastCGI server 上,fastcgi_param 指令设置 SCRIPT_FILENAME 参数为/data/www/index.php
,然后 FastCGI 去执行这个脚本文件。$document_root 变量的值就是 root 指令的值,$fastcgi_script_name 变量的值就是 request 的 uri,即 /index.php/about.html
:仅被"/"
location 匹配到,因此将在此 location 中处理,找到/data/www/about.html
文件并发送到客户端/
:更复杂了,其仅被"/"
location 匹配到,因此将在此 location 中处理,index 指令会根据自身和 root 指令的参数值检测 index 文件是否存在,如果/data/www/index.html
不存在,但是/data/www/index.php
存在,index 指令会做一次到/index.php
的内部的重定向,就好像客户端又发了一次请求似的,后续步骤参考第二个例子。