Nginx如何处理一个请求

823 阅读5分钟

基于名称如何去选择虚拟服务

当我们配置多个server时,nginx是如何去决定由哪个server去处理请求的呢?让我们从一个简单的配置开始,以下是三个虚拟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会做如下处理:

1、Nginx只通过校验请求头“Host“去决定应该去路由到那个server。

2、如果一个请求的Host值没有匹配到任何的serve_name值,或者请求中没有包含“Host“请求头,那么nginx路由这个请求到指定port的默认server。

3、在上面的配置中,默认的Server是第一个(nginx默认的行为)。

当然我们也可以通过default_server参数(直接放在listen后面)去指定一个默认的server,如下:

server {
    listen      80 default_server;
    server_name example.net www.example.net;
    ...
}

tip:default_server参数在0.8.21开始可用,更早的版本中是使用default参数。

需要注意被default_server参数标记的server,但没有server_name数据,更多说明看下个章节。

在没有定义的server_name情况下如何去阻止处理请求

在nginx中如果我们不希望请求中没有Host请求头字段,可以定义一个server去丢弃这类请求,如下定义:

server {
    listen      80;
    server_name "";
    return      444;
}

如上,server_name是设置空字符,将会匹配没有Host请求头字段的请求,并且指定了一个非标准http code 444去关闭连接。

自从0.8.48后,server_name默认值是空字符,因此如上server_name ““这个配置是可以省略掉的。在更早的版本中,机器的hostname被当作默认的server_name值。

基于名称和基于ip的混合虚拟服务

接下来我们来看一下一个混合的配置,每个server都监听不同的地址,如下:

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会去做如下处理:

1、首先会去校验ip地址和端口是否和listen指定的一致。

2、listen匹配成功后再校验Host请求头是否与server_name指定的一致。

3、如果server_name没有匹配成功,请求将会被默认server处理。例如在192.168.1.1:80上收到一个来自www.example.com的请求,将会被192.168.1.1:80的默认server处理,即这第一个server(并且没有定义www.example.com server_name的服务)去处理这个请求。

如前面所说,defaul_server被放置在一个listen的port上,那么不同的默认server可能有不同的port上,如下配置:

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站点配置

现在,让我们来看看nginx是如何去选择一个location去处理一个经典、简单的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会去做如下处理。

1、nginx首先会去搜索由字面字符串的最特定的前缀location,并且会忽略location的顺序。在上面那个配置中,只有“/“前缀的location符合,因为它可以匹配任何请求,因此它将会被放在最后的排序。

2、之后nginx会按照在配置文件中的顺序去检查所有的location给定的规则。最先匹配的表达式后会停止查找,并且使用这个location。

3、如果没有匹配到任何表达式,那么nginx将会使用前面早先找到的最特定的location。

注意:所有的location值只匹配请求行的uri部分,排除所有的url参数,这样做是因为查询字符串的参数将会有多种形式,比如:

/index.php?user=john&page=1
/index.php?page=1&user=john

现在,让我们来看看nginx针对上面的配置是如何去处理请求的:

处理/logo.gif请求

1、请求首先是被“/“location匹配。

2、然后是被正则表达式 “\.(gif|jpg|png)$”匹配到,因此它会被后一个location处理。直接使用“root /data/www”去映射请求,映射到文件/data/www/logo.gif,最后将这个文件发给客户端。

处理/index.php请求

1、请求首先也是被“/“location匹配。

2、然后是被正则表达式 “\.(php)”匹配到,并被该location最终处理。请求会通过FastCGI服务监听的localhost:9000fastcgi_param会将SCRIPT_FILENAME参数设置成“/data/www/index.php”,然后FastCGI服务会直接执行这个文件。其中变量”匹配到,并被该location最终处理。请求会通过FastCGI服务监听的localhost:9000,fastcgi\_param会将SCRIPT\_FILENAME参数设置成“/data/www/index.php”,然后FastCGI服务会直接执行这个文件。其中变量document_root等于根指令的值,变量$fastcgi_script_name等于请求的URI,即" /index.php "。

处理/about.html请求

1、首先请求只被“/“location匹配,因此将由“/“location去处理这个请求。

2、之后直接使用“root /data/www”去映射请求,映射到文件/data/www/about.html,最后将这个文件发给客户端。

处理一个“/“的请求

1、处理一个“/”是非常复杂的,它只被“/“location匹配,因此将由“/“location去处理这个请求。

2、然后会去通过“root /data/www“指令去检查index文件是否存在。如果/data/www/index.html存在,直接返回。

3、如果/data/www/index.html不存在,并且/data/www/index.php存在,那么将会重定向到“index.php“,然后nginx将这个请求当作是由客户端发送过来的,重新搜索所有的location。如前所述,这个重定向的请求最终将有FastCGI服务去处理

location优先级匹配

  1. =前缀的指令严格匹配这个查询。如果找到,停止搜索。
  2. 所有剩下的常规字符串,最长的匹配。如果这个匹配使用^〜前缀,搜索停止。
  3. 正则表达式,在配置文件中定义的顺序。
  4. 如果第3条规则产生匹配的话,结果被使用。否则,使用第2条规则的结果。

例如

location  = / {
  # 只匹配"/"
  [ configuration A ] 
}
location  / {
  # 匹配任何请求,因为所有请求都是以"/"开始
  # 但是更长字符匹配或者正则表达式匹配会优先匹配
  [ configuration B ] 
}
location ^~ /images/ {
  # 匹配任何以 /images/ 开始的请求,并停止匹配其它location
  [ configuration C ] 
}
location ~* .(gif|jpg|jpeg)$ {
  # 匹配以 gif、jpg或者jpeg结尾的请求
  # 但是所有 /images/ 目录的请求将由 [Configuration C]处理
  [ configuration D ] 
}

请求URI例子:

  • / -> 符合configuration A

  • /documents/document.html -> 符合configuration B

  • /images/1.gif -> 符合configuration C

  • /documents/1.jpg ->符合 configuration D

参考文档链接:nginx.org/en/docs/htt…