前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Nginx域名访问处理过程 原

Nginx域名访问处理过程 原

作者头像
随风溜达的向日葵
发布2018-08-15 11:35:45
3.1K0
发布2018-08-15 11:35:45
举报
文章被收录于专栏:技术墨客

基于域名的虚拟服务器(server)

在实际应用中,我们可以将多个域名指向一个IP 地址,或者使用范IP解析功能。当多个域名执行一个 IP 地址时,Nginx 可以根据域名来分配不同的虚拟服务器,如下面的例子。定义了三个虚拟服务器同时监听80端口:

代码语言:javascript
复制
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

代码语言:javascript
复制
server {
    #指定当前server为默认的server
    listen      80 default_server;
    server_name example.net www.example.net;
}

我们还可以在一个 listen 指令下配置多个端口的监听:

代码语言:javascript
复制
#
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 数据的请求,那么可以增加一个如下配置的虚拟服务器:

代码语言:javascript
复制
server {
    listen      80;
    #空字符串匹配无HOST参数的情况
    server_name ""; 
    #返回444状态码
    return      444;
}

这时,在接收到一个无 host 数据的请求时会返回一个444的异常状态码表示拒绝该次请求的链接。

基于IP和域名的混合路由服务

Nginx 同样支持根据访问 IP 来选择 server 的情况,下面是一个混合处理 IP 以及域名的例子:

代码语言:javascript
复制
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

代码语言:javascript
复制
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 的匹配规则:

代码语言:javascript
复制
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 这样的通配符。

正则表达式规则

正则表达式必须以(~)符号开头:

代码语言:javascript
复制
#正则表达式
server_name  ~^www\d+\.example\.net$;

否则 Nginx 会认为这是一个固定的字符串或通配符字符串。

在使用正则表达式时,通常会以 开头以 $ 结尾,虽然正则语法上并不要求一定要使用这2个符号,但是会大大提升解析效率。另外还要注意的是,由于点符号(.)是正则表达式的一个关键字,所以域名中的点需要使用反斜线来转意(\.)。

如果在正则表达式中需要使用大括号( "{" 和 "}" ),因为大括号是 Nginx 块符号,所以使用时需要用双引号将正则表达式引用起来:

代码语言:javascript
复制
server_name  "~^(?<name>\w\d{1,3}+)\.example\.net$";

否则启动时会输出异常。

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

代码语言:javascript
复制
server {
    #<domain>表示一个变量
    server_name   ~^(www\.)?(?<domain>.+)$;

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

Nginx 使用的正则表达式通过 Perl 来解析(PCRE)。不同版本的 perlPCRE)对正则表达式获取变量的语法有略微的差异。通常情况下现在安装的操作系统都支持最新的语法规则。如果在启动Nginx时日志出现:

代码语言:javascript
复制
pcre_compile() failed: unrecognized character after (?< in ...

这样的内容,表示当前的PCRE版本较低,需要用旧的表达式。

Server_name更多的规则说明

可以在一个 server 模块中一次指定多个匹配字符串、通配符以及正则表达式:

代码语言:javascript
复制
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 请求:

代码语言:javascript
复制
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,而有部分请求来源与其他二级域名,明确的将常用域名定义出来这可以得到不错的优化:

代码语言:javascript
复制
server {
    listen       80;
    server_name  example.org  www.example.org  *.example.org;
}

这样配置指令比简写为:

代码语言:javascript
复制
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“这样的域名匹配时,在启动的过程中会输出:

代码语言:javascript
复制
could not build the server_names_hash,
you should increase server_names_hash_bucket_size: 32

这样的异常内容。这个时候需要增加对应的size。

代码语言:javascript
复制
http {
    #增加名称哈希表大小
    server_names_hash_bucket_size  64;
}

如果 server 的名称太多,会输出:

代码语言:javascript
复制
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 只有标记意义,没有实际意义。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基于域名的虚拟服务器(server)
  • 避免请求中没有定义服务器名称的情况
  • 基于IP和域名的混合路由服务
  •  范域名解析
    • 通配符规则
      • 正则表达式规则
        • Server_name更多的规则说明
        • 基于server_name的性能优化
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档