Nginx域名访问处理过程 原

基于域名的虚拟服务器(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  ~^(?<user>.+)\.example\.net$;
}

通过各种规则来解析域名我们称之为“范域名解析”

我们可以在域名服务商那里设定范域名解析的规则。通常情况下是在主域名的之前使用通配符*来指定所有的二级域名指向同一个地址,例如 *.example.com。范域名解析有很强的应用场景,例如动态生成二级域名或多级域名等等。

在上面的这个配置设定下,一个请求如果能够同时匹配多个 server_name 的规则(例如同时匹配上一个通配符和一个正则表达式),Nginx 会使用顺序靠前的匹配 server 来处理该请求。下面是匹配的优先级:

  1. 固定的字符串(无通配符、非正则表达式)。
  2. 通配符的位置出现在字符串的起始位置,例如 *.example.org。多个匹配使用长度优先原则。
  3. 通配符的位置出现在字符串的末尾位置,例如 mail.*。多个匹配使用长度优先原则。
  4. 最先匹配的正则表达式(次序按照server在文档中出现先后位置确定)。

通配符规则

一个星号(*)表示一个通配符,他表示匹配一个或多个URL允许使用的字符的组合。通配符只能出现在字符串的开头和末尾,并且只能用点号(.)与其他字符串分割。

例如下面这样就是正确的通配符书写方式:

  1. *.example.org
  2.  mail.*

而下面这样的书写方式是错误的:

  1. www.*.example.org 。
  2. 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  "~^(?<name>\w\d{1,3}+)\.example\.net$";

否则启动时会输出异常。

使用正则表达式还支持变量传递,例如:

server {
    #<domain>表示一个变量
    server_name   ~^(www\.)?(?<domain>.+)$;

    location / {
        #使用$domain获取变量值映射到指定的磁盘路径
        root   /sites/$domain;
    }
}

Nginx 使用的正则表达式通过 Perl 来解析(PCRE)。不同版本的 perlPCRE)对正则表达式获取变量的语法有略微的差异。通常情况下现在安装的操作系统都支持最新的语法规则。如果在启动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)配置了大量的服务的名称(域名),或者单个服务器的名称非常长,建议调整上下文中server_names_hash_max_size 和  server_names_hash_bucket_size 参数。默认情况下 server_names_hash_bucket_size 的值是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: 512
or server_names_hash_bucket_size: 32

这个时候可以调整对应的参数数值。不过还是建议不要出现这样的情况,因为将相关的数值调整后会影响单次读写缓存的大小,造成命中数值所需的读写次数增加。

最后,如果在一个 server 中只有一个 server_name 的指令配置,Nginx 仅仅会考虑 listen 命中而不会去判断域名是否命中。此时可以使用任意一个字符串来表达 server_name 只有标记意义,没有实际意义。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏北京马哥教育

Linux Shell 通配符、元字符、转义符最全使用攻略

来自:程默 - 博客园,作者:程默 www.cnblogs.com/chengmo/archive/2010/10/17/1853344.html 说到she...

84360
来自专栏菩提树下的杨过

maven: 打包可运行的jar包(java application)及依赖项处理

IDE环境中,可以直接用exec-maven-plugin插件来运行java application,类似下面这样: 1 <plugin> 2 <g...

23190
来自专栏JavaEE

jsp技术前言:一、简介:二、hello world:三、jsp语法:四、九大内置对象:总结:

我是一名Java后台学习者,但是后台程序员也需要掌握一定的前端技术。虽然说现在前端基本上是react、vue、angular三分天下,但是作为一名Java程序员...

14730
来自专栏Rgc

scrapy回调函数传递参数

scrapy.Request 的callback传参的两种方式 1.使用 lambda方式传递参数 def parse(self, response): ...

28830
来自专栏Python小屋

Python使用模块中对象的几种方法

Python默认安装仅包含部分基本或核心模块,启动时也仅加载了基本模块,在需要时再显式地加载(有些模块可能需要先安装)其他模块,这样可以减小程序运行的压力,且具...

37360
来自专栏Python小屋

Python内置函数eval()用法及其安全问题

Python内置函数eval()用来对表达式进行求值: >>> eval('3+5') 8 >>> a = 3 >>> b = 5 >>> eval('a+b'...

1.2K90
来自专栏Python专栏

用python帮助你从此快起来!

18160
来自专栏腾讯移动品质中心TMQ的专栏

从Java乱码谈起

在实际项目开发中,特别是涉及到中文输入输出的时候,大家肯定都被各种乱码问题坑过。如果遇到复杂的系统,为了乱码问题折腾几天也不是不可能。

50760
来自专栏前端大白专栏

angular使用管道实现搜索功能

49260
来自专栏程序员的知识天地

Python使用os模块、Try语句、pathlib模块判断文件是否存在

通常在读写文件之前,需要判断文件或目录是否存在,不然某些处理方法可能会使程序出错。所以最好在做任何操作之前,先判断文件是否存在。

18120

扫码关注云+社区

领取腾讯云代金券