基于域名的虚拟服务器(server)
在实际应用中,我们可以将多个域名指向一个IP 地址,或者使用范IP解析功能。当多个域名执行一个 IP 地址时,Nginx 可以根据域名来分配不同的虚拟服务器,如下面的例子。定义了三个虚拟服务器同时监听80端口:
http { #同时监听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 head)中Host 的数据来确定使用哪个server来处理当前请求。如果请求没有匹配任何 server,或者访问头(request head)中没有包含Host的数据,那么 Nginx 会将该请求路由给默认的 server,默认情况下就是配置文件中的第一个 server。
可以通过在 listen 指令中增加 default_server 参数来指定默认的 server:
server { #指定当前server为默认的server listen 80 default_server; server_name example.net www.example.net;}
我们还可以在一个 listen 指令下配置多个端口的监听:
#server { listen 80; listen 8080 default_server; server_name example.net;}#server { listen 80 default_server; listen 8080; server_name example.org;}
避免请求中没有定义服务器名称的情况
如果服务器不允许没有 request head 中没有 host 数据的请求,那么可以增加一个如下配置的虚拟服务器:
server { listen 80; #空字符串匹配无HOST参数的情况 server_name ""; #返回444状态码 return 444;}
这时,在接收到一个无 host 数据的请求时会返回一个444的异常状态码表示拒绝该次请求的链接。
基于IP和域名的混合路由服务
Nginx 同样支持根据访问 IP 来选择 server 的情况,下面是一个混合处理 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 首先测试与 listen 指令 能够匹配的 IP 地址和端口,然后在能够匹配上 IP 和端口的条目下,再检查server_name是否匹配访问头(request head)的 host 参数。如果 server_name 无法匹配上,那么会使用“默认”的server来响应当前的请求——即第一个匹配上 IP 地址的 server。例如当前请求的 HOST 是 www.example.com 并发送给 192.168.1.1:80 地址,那么用来处理这个请求的是第一个 server,原因是域名和端口匹配上,但是 server_name 无法匹配,选用第一个能匹配的 server。
在混合的规则下,可以在 listen 指令上为不同的地址端口定义多个默认的 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;}
切记,default_server 参数只能用在 listen 指令上,也就是根据 IP 以及端口来指定默认的虚拟服务器。相同的IP以及端口可以设置一个默认虚拟服务器。
范域名解析
前面介绍了根据域名( host 属性)路由 server 的情况,他们都使用 www.example.com 这样明确的字符串来定义域名。除此之外,还可以使用通配符以及正则表达式来设定 server_name 的匹配规则:
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 ~^(?.+)\.example\.net$;}
通过各种规则来解析域名我们称之为“范域名解析”。
我们可以在域名服务商那里设定范域名解析的规则。通常情况下是在主域名的之前使用通配符*来指定所有的二级域名指向同一个地址,例如 *.example.com。范域名解析有很强的应用场景,例如动态生成二级域名或多级域名等等。
在上面的这个配置设定下,一个请求如果能够同时匹配多个 server_name 的规则(例如同时匹配上一个通配符和一个正则表达式),Nginx 会使用顺序靠前的匹配 server 来处理该请求。下面是匹配的优先级:
- 固定的字符串(无通配符、非正则表达式)。
- 通配符的位置出现在字符串的起始位置,例如 *.example.org。多个匹配使用长度优先原则。
- 通配符的位置出现在字符串的末尾位置,例如 mail.*。多个匹配使用长度优先原则。
- 最先匹配的正则表达式(次序按照server在文档中出现先后位置确定)。
通配符规则
一个星号(*)表示一个通配符,他表示匹配一个或多个URL允许使用的字符的组合。通配符只能出现在字符串的开头和末尾,并且只能用点号(.)与其他字符串分割。
例如下面这样就是正确的通配符书写方式:
- *.example.org 。
- mail.*。
而下面这样的书写方式是错误的:
- www.*.example.org 。
- w*.example.org。
一个星号可以匹配多个URL字符,所以 *.example.org 即可匹配 www.example.org 也可以匹配 www.sub.example.org。一个特殊的情况是 .example.org 这样的域名,即可匹配 example.org 这样固定的字符串,也可匹配 *.example.org 这样的通配符。
正则表达式规则
正则表达式必须以(~)符号开头:
#正则表达式server_name ~^www\d+\.example\.net$;
否则 Nginx 会认为这是一个固定的字符串或通配符字符串。
在使用正则表达式时,通常会以 ^ 开头以 $ 结尾,虽然正则语法上并不要求一定要使用这2个符号,但是会大大提升解析效率。另外还要注意的是,由于点符号(.)是正则表达式的一个关键字,所以域名中的点需要使用反斜线来转意(\.)。
如果在正则表达式中需要使用大括号( "{" 和 "}" ),因为大括号是 Nginx 块符号,所以使用时需要用双引号将正则表达式引用起来:
server_name "~^(?\w\d{1,3}+)\.example\.net$";
否则启动时会输出异常。
使用正则表达式还支持变量传递,例如:
server { #表示一个变量 server_name ~^(www\.)?(? .+)$; location / { #使用$domain获取变量值映射到指定的磁盘路径 root /sites/$domain; }}
Nginx 使用的正则表达式通过 Perl 来解析(PCRE)。不同版本的 perl(PCRE)对正则表达式获取变量的语法有略微的差异。通常情况下现在安装的操作系统都支持最新的语法规则。如果在启动Nginx时日志出现:
pcre_compile() failed: unrecognized character after (?< in ...
这样的内容,表示当前的PCRE版本较低,需要用旧的表达式。
Server_name更多的规则说明
可以在一个 server 模块中一次指定多个匹配字符串、通配符以及正则表达式:
server { listen 80; server_name example.org www.example.org "";}
在上面的例子中,"" 用来处理请求头中(request head)中不包含 Host 参数的情况。如果 server 块没有指定 server_name 参数。那么当前的 server 默认使用空字符串作为虚拟注意的 server_name。
如果将 server_name 的参数指定为 $hostname,那么会使用当前主机的 hostname。
使用 server_name 也可以处理 IP 请求:
server { listen 80; server_name example.org www.example.org "" 192.168.1.1 ;}
当客户端直接用 IP 访问时,对应的 server_name 会处理。
基于server_name的性能优化
无论是固定的字符串,还是星号通配符以及正则表达式,所有的匹配规则都会根据 server 的监听端口创建一个哈希表(hash table)。这个哈希表在Nginx加载阶段进行了优化,以便在CPU运算时以最少的读写次数命中哈希值。
Nginx 在匹配一个请求时,固定字符串的哈希表是最先进行匹配的。如果没有固定的字符串匹配,那么会开始匹配以星号通配符开始的哈希表。未匹配上的话就继续匹配以通配符星号结尾的哈希表。
匹配通配符的过程肯定比匹配一个固定的哈希值的过程慢许多。需要特别注意的是:“.example.org”这样的字符串是被存储在通配符的哈希表中的,而不是固定字符串的hash表,所以不要出现这样的书写。
如果固定哈希表和通配符哈希表都无法匹配得上,最后就会去匹配正则表达式,也也是最慢的。
因此,建议将一些经常会出现的域名以固定字符串的方式记录。例如外部的访问请求大量来源于域名 example.org 或 www.example.org,而有部分请求来源与其他二级域名,明确的将常用域名定义出来这可以得到不错的优化:
server { listen 80; server_name example.org www.example.org *.example.org;}
这样配置指令比简写为:
server { # listen 80; server_name .example.org;}
性能会提升不少。
如果我们在某个主块内的上下文中(例如http)配置了大量的服务的名称(域名),或者单个服务器的名称非常长,建议调整上下文中 和 参数。默认情况下 的值是32、64或者其他值,这取决于服务器的 CPU 缓存大小。如果当前值为32,那么当出现”too.long.server.name.example.org“这样的域名匹配时,在启动的过程中会输出:
could not build the server_names_hash,you should increase server_names_hash_bucket_size: 32
这样的异常内容。这个时候需要增加对应的size。
http { #增加名称哈希表大小 server_names_hash_bucket_size 64;}
如果 server 的名称太多,会输出:
could not build the server_names_hash,you should increase either server_names_hash_max_size: 512or server_names_hash_bucket_size: 32
这个时候可以调整对应的参数数值。不过还是建议不要出现这样的情况,因为将相关的数值调整后会影响单次读写缓存的大小,造成命中数值所需的读写次数增加。
最后,如果在一个 server 中只有一个 server_name 的指令配置,Nginx 仅仅会考虑 listen 命中而不会去判断域名是否命中。此时可以使用任意一个字符串来表达 server_name 只有标记意义,没有实际意义。