Nginx 是怎样处理一个请求的

本文是对 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 是怎么被处理的:

  1. /logo.gif:被 "/"~* \.(gif|jpg|png)$ 两个 location 匹配到,因此会被后者处理,根据 root /data/www 指令,该 request 会被映射到 /data/www/logo.gif 文件,并将该文件发送给客户端
  2. /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
  3. /about.html:仅被 "/" location 匹配到,因此将在此 location 中处理,找到 /data/www/about.html 文件并发送到客户端
  4. /:更复杂了,其仅被 "/" location 匹配到,因此将在此 location 中处理,index 指令会根据自身和 root 指令的参数值检测 index 文件是否存在,如果 /data/www/index.html 不存在,但是 /data/www/index.php 存在,index 指令会做一次到 /index.php 的内部的重定向,就好像客户端又发了一次请求似的,后续步骤参考第二个例子。

参考资料

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注