nginx使用指南三:nginx是如何处理请求的

一 带名字的虚拟服务器

当一个从客户端发送请求到nginx时,nginx首先判定使用conf文件里的哪个server来处理,假设配置如下:

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的host字段,如果host不匹配任何server_name,则选择缺省的server进行处理,默认情况下,缺省的server就是第一个。当然,你也可以用listen指令去显式的修改这个缺省server:

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

注意,这里的缺省server是指定端口的一个属性,而不是server_name的属性,这点在后面会详细介绍。

1.1 如何拒绝未定义的server名

如果我们需要“拒绝不带host字段的请求”,可以这样定义:

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

这里,server_name为空字符串表示它将匹配不带host字段的请求,返回444表示错误并立刻关闭连接。

1.2 基于名字和ip的server名

listen指令不光可以指定端口,还可以指定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首先test server block下面的listen指令所指定的ip和端口,即先判定当前收到的请求是不是从本机的某个ip(可能是loopback ip)和端口收到的;接着test Host和server_name,如果server_name没匹配到,request将会被交给缺省server进行处理。举个例子,这个针对www.example.com的请求,在192.168.1.1:80端口收到,在上述的配置中,将尝试匹配第1、2个server,尽管ip+port匹配,但host匹配不上,最终匹配失败,那么就使用针对ip+port=192.168.1.1:80的缺省server(即第一个server block)。

正如我们前面所言,default是监听ip+port的一个属性,这就说明,不同的ip+port可以各自定义为default。例如下面这样子:

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;
    ...
}

如上,第2个block listen 192.168.1.1:80和第3个block listen 192.168.1.2:80分别都设定了自己的default server。
这样,如果当request的host=www.example.com,ip+port=192.168.1.1:80时,则选择第2个server block进行处理,而不是第一个。

Server names

我们已经知道,在host字段匹配过程中,server names由server_name指令来定义,它决定了对于一个请求来说,哪一个server block将会被用到。server names可以用精确名称、通配符、正则表达式等来进行匹配。
举例:

server {
    listen       80;
    server_name  example.org  www.example.org;
    ...
}

server {
    listen       80;
    server_name  *.example.org;
    ...
}

server {
    listen       80;
    server_name  mail.*;
    ...
}

server {
    listen       80;
    server_name  ~^(?<user>.+)\.example\.net$;
    ...
}

当通过名称搜索server时,如果名称能匹配到多个变量,比如同时匹配上通配符和正则表达式,那么按照下面的顺序,第一个匹配的变量将会被选中:
1 精确匹配名
2 以开始的最长的通配符名,例如 .example.org
3 以’‘结尾的最长的通配符名,例如mail.
4 按照配置文件中罗列的顺序,第一个匹配到的正则表达式

wildcard names

一个通配符名称只允许在名称的前面或者最后加上’*’号,而且仅仅只有一个符号’.’作为边界。www.*.example.org 和 w*.example.org是非法的。不过,它可以用正则表达式来替代,例如:
“~^www\..+.example.org $ 或者 ~^w.*.example.org$ 。

一个星号可以匹配几个部分名称,例如”*.example.org” 不仅匹配www.example.org,也匹配www.sub.example.org。

注意,这里有一个特殊的形式”.example.org”,不仅可以匹配”example.org”,也可以匹配*.example.org。

regular expression names

nginx使用的正则表达式和Perl编程语言类似(PCRE),为了用正则表达式来表示,服务名必须以破浪号打头:
server_name ~^www\d+.example.net$;
否则,它将被当做一个精确的名称进行匹配,又或者表达式中含有星号,那将被当做通配符进行匹配。别忘了设置’\^’ 和’$’,他们虽然说在语法上不是必须的,但是逻辑上需要。另外还需注意,域名.应该使用转义\.,一个包含”{“和”}”的正则表达式可以像这样表述:

server_name  "~^(?<name>\w\d{1,3}+).example.net$";

否则的话,nginx将会报错:

directive "server_name" is not terminated by ";" in ...

A named regular expression capture can be used later as a variable:
一个已经匹配过的正则表达式命中,可以在接下来的匹配中作为一个变量进行使用:

server {
    server_name   ~^(www\.)?(?<domain>.+)$;

    location / {
        root   /sites/$domain;
    }
}

PCRE库按照以下的语法支持这种命名变量的捕获规则:

?<name>     Perl 5.10 compatible syntax, supported since PCRE-7.0
?'name'     Perl 5.10 compatible syntax, supported since PCRE-7.0
?P<name>    Python compatible syntax, supported since PCRE-4.0

如果nginx启动失败的话,将弹出以下错误:

pcre_compile() failed: unrecognized character after (?< in ...

这意味着,PCRE库太老可,应该尝试使用\

?P<name>

来进行替换, 这种捕获方式也可以用数字形式来表示:

server {
    server_name   ~^(www\.)?(.+)$;

    location / {
        root   /sites/$2;
    }
}

不过,对于比较简单的情况,应尽量少的使用上述捕获规则,因为数字引用很容易被覆盖。

Miscellaneous names

有的名字会被特殊处理,如果需要处理一个不包括Host的请求,那么就需要这样来设置server block:

server {
    listen       80;
    server_name  example.org  www.example.org  "";
    ...
}

如果在server block里面没有定义server_name的话,nginx将使用空的字符串作为server名。
如果server名被定义为”$hostname”,那么该机器的hostname将会被引用到。
假设现在有一个使用ip地址而不是hostname的请求,Host 请求头里面会包含有IP地址,设置IP地址作为server name的话,该请求将会得到处理:

server {
    listen       80;
    server_name  example.org
                 www.example.org
                 ""
                 192.168.1.1
                 ;
    ...
}

在一个catch-all server的例子中,有一个奇怪的名字”_”将会出现:

server {
    listen       80  default_server;
    server_name  _;
    return       444;
}

其实,这并没有什么特殊的,它只是无数非法domain名称中的一个。其它非法的等价名还有:
“–” and “[email protected]#” 。

nginx versions up to 0.6.25 supported the special name “” which was erroneously interpreted to be a catch-all name. It never functioned as a catch-all or wildcard server name. Instead, it supplied the functionality that is now provided by the server_name_in_redirect directive. The special name “” is now deprecated and the server_name_in_redirect directive should be used. Note that there is no way to specify the catch-all name or the default server using the server_name directive. This is a property of the listen directive and not of the server_name directive. See also “How nginx processes a request”. It is possible to define servers listening on ports :80 and :8080, and direct that one will be the default server for port :8080, while the other will be the default for port :80:

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

server {
    listen       80  default_server;
    listen       8080;
    server_name  example.org;
    ...
}

name optimization

精确匹配名,以星号开头的通配符名,以星号结尾的通配符名,这三者在实现时,会被放到三个不同的hash表中,hash表的大小会得到优化,以期在匹配时,cpu尽量多的cache到hash。在匹配的时候,顺序也是先从精确匹配名hash表开始,接着是”以星号开头的通配符名”hash表,最后是”以星号结尾的通配符名”hash表。按照这个顺序,只要找到一个匹配项,那么匹配过程就会终止。

在通配符hash表中的搜索速度明显要低于精确匹配名hash表,需要注意像”.example.org”这种形式,实际上是放在wildcard hash中的,而不在exact name hash中。

正则表达式会按照顺序逐个test,这里的cpu消耗的时间复杂度不好评估,一般来讲,为了提高处理请求的效率,应尽量把exact name放在前面,例如访问频率最高的host为”.example.org” 和 “www.example.org”,那么高效的方式是这样显式的定义它们:

server {
    listen       80;
    server_name  example.org  www.example.org  *.example.org;
    ...
}

比起下面简单定义一下,效率要高多了:

server {
    listen       80;
    server_name  .example.org;
    ...
}

If a large number of server names are defined, or unusually long server names are defined, tuning the server_names_hash_max_size and server_names_hash_bucket_size directives at the http level may become necessary. The default value of the server_names_hash_bucket_size directive may be equal to 32, or 64, or another value, depending on CPU cache line size. If the default value is 32 and server name is defined as “too.long.server.name.example.org” then nginx will fail to start and display the error message:
如果有一大堆的server names要定义,或者有很多不常用的server names也需要定义,那么就很有必要设定一下server_names_hash_max_size 和 server_names_hash_bucket_size。缺省的
server_names_hash_bucket_size 可能为32,64,或者其它值,这取决于cpu cache line的大小。如果缺省值为32,而server name 为定义为”too.long.server.name.example.org”,这样nginx将会启动报错并打印错误:

could not build the server_names_hash,
you should increase server_names_hash_bucket_size: 32

在这里例子中,server_names_hash_bucket_size的值应该在原有基础上乘以2:

http {
    server_names_hash_bucket_size  64;
    ...

If a large number of server names are defined, another error message will appear:
如果又定义了一大推的server名的话,那么又会出现这样的错误消息:

could not build the server_names_hash,
you should increase either server_names_hash_max_size: 512
or server_names_hash_bucket_size: 32

在这个例子中,先尝试设置 server_names_hash_max_size到一个接近server名个数的值,如果还是失败,或者nginx的启动时间非常长的话,尝试增加server_names_hash_bucket_size的数值。

如果监听一个端口的只有一个server,那么nginx就根本不会test server名(而且也不会构建hash表了)。不过有个例外,就是当server名是一个正则表达式的时候,nginx就不得不处理这个表达式来进行捕获。

二 location匹配细节

本节主要介绍location的一些匹配细节:原文可参考nginx指定帮助:

Syntax: location [ = | ~ | ~* | ^~ ] uri { ... }
location @name { ... }
Default:    —
Context:    server, location

匹配规则在使用之前,会有一个预处理,预处理操作会对URI进行规范化,包括有:
1 对类似”%XX”URI进行解码
2 解析先对路径和转移符’\’
预处理完成后,才会对location进行匹配。
location的匹配规则可定义为两种:其一是定义一个前缀字符串,用于和uri进行匹配;其二是使用正则表达式对uri进行匹配。
其中,正则表达式以 ~ | ~* | ^~ 开头,定义如下:

符号 含义
~ 匹配正则表达式,区分大小写
~* 匹配正则表达式,不区分大小写
^~ 如果能匹配到当前的表达式作为最优的前缀表达式,则直接选择这个作为最终匹配结果,不匹配正则表达式了

为了找到最优匹配项,nginx首先检查所有带前缀的匹配项,选择最长的匹配项最为保留选项s1;如果有正在表达式,则逐个匹配正则表达式,找到则即为选项s2,将s2作为最优选择;如果正则表达式不匹配,则选择s1作为最终结果。

注意:如果保留选项s1有前缀”^~”,则直接选择s1作为最优结果,而不用匹配正则表达式。

三 nginx如何处理TCP/UDP session

A TCP/UDP session from a client is processed in successive steps called phases:
一个来自于客户端的TCP/UDP session会依次按照以下步骤进行处理:

Post-accept

第一步,在接受连接之后,ngx_stream_realip_module 将在此阶段触发

Pre-access

进行初步的访问权限检查,ngx_stream_limit_conn_module 在此阶段触发

Access

在进行实际的数据处理之前,做进一步的访问检查,在此阶段ngx_stream_access_module 将被触发

SSL

TLS/SSL termination. The ngx_stream_ssl_module module is invoked at this phase. 

Preread

在原始buffer中预读一部分数据,并允许其它模块在进一步处理之前做数据分析,例如ngx_stream_ssl_preread_module 模块

Content

该阶段是数据得到实际处理的阶段,也是必不可少的一步,通常用在代理upstream servers,或者返回一直指定的值到客户端。

Log

最后阶段,用于记录一个客户端回话的处理结果,在此阶段ngx_stream_log_module 模块将被触发。
文章来源: nginx使用指南三:nginx是如何处理请求的

人吐槽 人点赞

猜你喜欢

发表评论

用户名: 密码:
验证码: 匿名发表

你可以使用这些语言

查看评论:nginx使用指南三:nginx是如何处理请求的