概述

本文的目标读者是Tengine/Nginx 研发或者运维同学,如果自己对这块逻辑非常清楚,那可以略过,如果在配置或者开发 Tengine/Nginx 过程中,有如下疑问的同学,本文或许能解答你多年的疑惑:

  1. 请求到达匹配的是哪个 server 块?
  2. 为啥明明配置了 server 块,还是没有生效?
  3. 没有这个域名的 server 块,请求到底使用了哪个 server 块?
  4. 要自己去匹配 server 块的话,该从哪里入手?
    ……

等等此类 server 块有关的问题,在使用 Tengine 时可能经常有遇到,在配置的 server 块较少时,比较容易识别出,但在 CDN 或者云平台接入层这种场景下,配置的 server 块一般都非常多,少的有几十上百个,多的成千上万个都有可能,所以了解 Tengine 如何查找 server 块非常有利于日常问题排查。

配置

先来看看几个配置:

server {
listen 10.101.192.91:80 default_server;
listen 80 default_server;
listen 8080 default_server; server_name www.aa.com; default_type text/plain; location / {
return 200 "default-server: $server_name, host: $host";
}
} server {
listen 10.101.192.91:80; server_name www.bb.com; default_type text/plain; location / {
return 200 "80server: $server_name, host: $host";
}
} server {
listen 10.101.192.91:8080; server_name *.bb.com; default_type text/plain; location / {
return 200 "8080server: $server_name, host: $host";
}
} server {
listen 10.101.192.91:8080; server_name www.bb.com; default_type text/plain; location / {
return 200 "8080server: $server_name, host: $host";
}
}

上面配置了四个 server 块,配置也非常简单,第一个 server 块配置了 default_server 参数,这个表明了这个是默认 server 块的意思(准确地说是这个 listen 的 IP:Port 进来的请求默认 server 块),监听了两个端口80和8080,匹配域名为 www.aa.com,第二个是监听了 10.101.192.91:80 和匹配域名为www.bb.com 的 server 块,第三个是监听了 10.101.192.91:8080 和匹配泛域名 *.bb.com 的 server 块,第四个是监听了 10.101.192.91:8080 和匹配精确域名 www.bb.com 的 server 块。下面来验证一下:

可以看出:

  1. 127.0.0.1:80 和 127.0.0.1:8080 都访问到了第一个 server 块

    • 这是因为第一个 server 监听了 :80 和 :8080 端口,其他 server 块没有监听 127.0.0.1 相应的端口,127.0.0.1 的访问只能匹配第一个 server 块。
  2. 10.101.192.91:80 的访问,域名和 server 块匹配时使用了相应的 server 块,不匹配时使用了第一个默认 server 块

    • IP:Port 匹配的情况下,再匹配到域名所在的 server 块,域名跟 server_name 不匹配则匹配默认 server 块。
  3. 10.101.192.91:8080 的访问,域名先精确匹配到了 www.bb.com 的 server 块,然后再匹配到了泛域名 *.bb.com 的 server 块,不匹配时使用了第三个隐式默认 server 块

    • 这里涉及到泛域名和隐式默认 server 块,泛域名的匹配是在精确域名之后,这个也比较好理解,隐式默认 server 块是没有在 listen 后面指定 default_server 参数的 server 块, Tengine/Nginx 在解析配置时,每个 IP:Port 都有一个默认 server 块,如果 listen 后面显式指定了 default_server 参数则该 listen 所在的 server 就是这个 IP:Port 的默认 server 块,如果没有显式指定 default_server 参数则该 IP:Port 的第一个 server 块就是隐式默认 server 块。

上面这些配置可以衍生出一些 debug 技巧:

if ($http_x_alicdn_debug_get_server = "on") {
return 200 "$server_addr:$server_port, server_name: $server_name";
}

只要带上请求头 X-Alicdn-Debug-Get-Server: on 即可知道请求命中的是哪个 server 块,这个配置对 server 块非常多的系统 debug 非常有用,需要注意的是这个配置需要放到一个配置文件和用 server_auto_include 加载,然后 tengine 会自动在所有 server 块生效(nginx 没有类似的配置命令)。

数据结构

我们再来看看 http 核心模块 server 块的配置在数据结构上怎么关联的,其数据结构是:

typedef struct {
/* array of the ngx_http_server_name_t, "server_name" directive */
ngx_array_t server_names; /* server ctx */
ngx_http_conf_ctx_t *ctx; u_char *file_name;
ngx_uint_t line; ngx_str_t server_name;
#if (T_NGX_SERVER_INFO)
ngx_str_t server_admin;
#endif size_t connection_pool_size;
size_t request_pool_size;
size_t client_header_buffer_size; ngx_bufs_t large_client_header_buffers; ngx_msec_t client_header_timeout; ngx_flag_t ignore_invalid_headers;
ngx_flag_t merge_slashes;
ngx_flag_t underscores_in_headers; unsigned listen:1;
#if (NGX_PCRE)
unsigned captures:1;
#endif ngx_http_core_loc_conf_t **named_locations;
} ngx_http_core_srv_conf_t;

这里不细说这些字段是干嘛用的,主要看 ngx_http_core_srv_conf_t 怎么与其他数据结构关联,从上面的配置可以知道 server 是与 IP:Port 有关联的,在 tengine/nginx 里的关系如下:

typedef struct {
ngx_http_listen_opt_t opt; ngx_hash_t hash;
ngx_hash_wildcard_t *wc_head;
ngx_hash_wildcard_t *wc_tail; #if (NGX_PCRE)
ngx_uint_t nregex;
ngx_http_server_name_t *regex;
#endif /* the default server configuration for this address:port */
ngx_http_core_srv_conf_t *default_server;
ngx_array_t servers; /* array of ngx_http_core_srv_conf_t */
} ngx_http_conf_addr_t;

可以看出,IP:Port 的核心数据结构 ngx_http_conf_addr_t 里面有默认 server 块 default_server,以及该 IP:Port 关联的所有 server 块数组 servers,其他几个字段不细展开了。tengine 把所有的 IP:Port 按 Port 拆分后将 ngx_http_conf_addr_t 放到了 ngx_http_conf_port_t 里面了:

typedef struct {
ngx_int_t family;
in_port_t port;
ngx_array_t addrs; /* array of ngx_http_conf_addr_t */
} ngx_http_conf_port_t;

为什么将 IP:Port 拆分呢,这是因为 listen 的 Port 如果没有指定 IP,比如 listen 80; ,那 tengine/nginx 在创建监听 socket 时的地址是 0.0.0.0 ,如果还有其他配置 listen 了精确 ip 和端口,比如 listen 10.101.192.91:80; ,那在内核是没法创建这个 socket 的,第2节配置里面的几个 listen 在内核是这样监听的:

虽然 listen 了 80 和 10.101.192.91:80,但在内核都是 0.0.0.0:80,所以 tengine 需要用 ngx_http_conf_port_t 来记录该端口的所有精确地址。但这个结构只是使用在配置阶段,在监听 socket 时转换成了结构 ngx_http_port_t 和 ngx_http_in_addr_t(这是因为 ip:port 和 server 块是多对多的关系,需要重新组织和优化):

typedef struct {
/* ngx_http_in_addr_t or ngx_http_in6_addr_t */
void *addrs;
ngx_uint_t naddrs;
} ngx_http_port_t; typedef struct {
in_addr_t addr;
ngx_http_addr_conf_t conf;
} ngx_http_in_addr_t; typdef ngx_http_addr_conf_s ngx_http_addr_conf_t; struct ngx_http_addr_conf_s {
/* the default server configuration for this address:port */
ngx_http_core_srv_conf_t *default_server; ngx_http_virtual_names_t *virtual_names; unsigned ssl:1;
unsigned http2:1;
unsigned proxy_protocol:1;
};

其中,ngx_http_port_t 记录了该端口的所有精确地址和对应的 server 块。而 ngx_http_port_t 放到了监听的 socket 核心结构 ngx_listening_t 中:

typedef struct ngx_listening_s  ngx_listening_t;

struct ngx_listening_s {
ngx_socket_t fd; struct sockaddr *sockaddr;
socklen_t socklen; /* size of sockaddr */
size_t addr_text_max_len;
ngx_str_t addr_text; // 省略…… /* handler of accepted connection */
ngx_connection_handler_pt handler; void *servers; /* array of ngx_http_in_addr_t, for example */ // 省略……
}; struct ngx_connection_s {
// 省略…… ngx_listening_t *listening; // 省略……
};

所以一个连接可以从 c->listening->servers 来查找匹配的 server 块。

tengine 中 ip:port 和 server 的大体关联关系如下:

(可以通过这个图来理解一下 tengine 如何查找 server 块)

从请求到 server 块

上面讲了 ip:port 和 server 的一些关系和核心数据结构,这一节来讲讲 tengine 从处理请求到匹配 server 的逻辑。ngx_http_init_connection 是初始化连接的函数,在这个函数里面我们看到有这样的逻辑:

void
ngx_http_init_connection(ngx_connection_t *c)
{
// 省略……
ngx_http_port_t *port;
ngx_http_in_addr_t *addr;
ngx_http_connection_t *hc;
// 省略…… /* find the server configuration for the address:port */ port = c->listening->servers; if (port->naddrs > 1) {
// 省略……
sin = (struct sockaddr_in *) c->local_sockaddr; addr = port->addrs; /* the last address is "*" */ for (i = 0; i < port->naddrs - 1; i++) {
if (addr[i].addr == sin->sin_addr.s_addr) {
break;
}
} hc->addr_conf = &addr[i].conf;
// 省略……
} else {
// 省略……
addr = port->addrs;
hc->addr_conf = &addr[0].conf;
// 省略……
} /* the default server configuration for the address:port */
hc->conf_ctx = hc->addr_conf->default_server->ctx;
// 省略……
}

可以看出,初始化时,拿到了 socket 的 ip:port 后去匹配了最合适的配置,存到了 hc->addr_conf 指针中,这个就是上面讲到的数据结构 ngx_http_addr_conf_t 指针,这里面存了该 ip:port 关联的所有 server 块核心配置,在之后收到 HTTP 请求头处理请求行或者处理 Host 头时,再根据域名去 hc->addr_conf 里面匹配出真实的 server 块:

static ngx_int_t
ngx_http_set_virtual_server(ngx_http_request_t *r, ngx_str_t *host)
{
// 省略……
ngx_http_connection_t *hc;
ngx_http_core_srv_conf_t *cscf; // 省略…… hc = r->http_connection; // 省略…… rc = ngx_http_find_virtual_server(r->connection,
hc->addr_conf->virtual_names,
host, r, &cscf); //创建 r 时,r->srv_conf 和 r->loc_conf 是 hc->conf_ctx 的默认配置
//查不到匹配的 server 块则不需要设置 r->srv_conf 和 r->loc_conf
if (rc == NGX_DECLINED) {
return NGX_OK;
} // 查到匹配的 server,使用真实 server 块的配置
r->srv_conf = cscf->ctx->srv_conf;
r->loc_conf = cscf->ctx->loc_conf;
// 省略……
}

函数 ngx_http_find_virtual_server 是查找域名对应的 server 块接口(这个函数还有另一个地方调用是在处理 SSL 握手遇到 SNI 时,这是因为在握手时也需要找到匹配的 server 块里面配置的证书)。
至此,server 块配置的查找逻辑结束,后续其他模块处理时可以从 r->srv_conf 和 r->loc_conf 查到自己模块的 server/location 块配置了。

本文作者:金九

原文链接

本文为云栖社区原创内容,未经允许不得转载。

Tengine 如何查找 server 块的更多相关文章

  1. nginx的server块如何支持php

    直接贴代码,备份用 server { listen ; server_name abc.com; index index.html index.htm index.php; root /var/www ...

  2. nginx中server块的匹配顺序

    客户端发出一个http请求时,nginx收到后会取出header头中的host,与nginx.conf中每个server的server_name进行匹配,以此决定到底由哪一个server块来处理这个请 ...

  3. Nginx SERVER块配置

    1 Listen 指令 Example Configuration Directives 2 server_name指令 2.1 规则 指令后可以跟多个域名,第一个是主域名 *泛域名:进支持在最前或最 ...

  4. 用 Linux blkid 命令查找块设备详情

    今天我们将会向你展示如何使用 lsblk 和 blkid 工具来查找关于块设备的信息,我们使用的是一台安装了 CentOS 7.0 的机器. lsblk lsblk 是一个 Linux 工具,它会显示 ...

  5. 【技术干货】听阿里云CDN安防技术专家金九讲tengine+lua开发

    一.介绍 二.安装 三.运行 四.开发 1.介绍 Tengine:轻量级.高性能.高并发.配置化.模块化.可扩展.可移植的Web和反向代理 服务器,Tengine是nginx超集,但做了很多优化,包含 ...

  6. 前端开发掌握nginx常用功能之server&location匹配规则

    nginx主要是公司运维同学必须掌握的知识,涉及到反向代理.负载均衡等服务器配置.前端开发尤其是纯前端开发来说对nginx接触的并不多,但是在一些情况下,nginx还是需要前端自己来搞:例如我们公司的 ...

  7. nginx负载均衡(5种方式)、rewrite重写规则及多server反代配置梳理

    Nginx除了可以用作web服务器外,他还可以用来做高性能的反向代理服务器,它能提供稳定高效的负载均衡解决方案.nginx可以用轮询.IP哈希.URL哈希等方式调度后端服务器,同时也能提供健康检查功能 ...

  8. Tengine HTTPS原理解析、实践与调试【转】

    本文邀请阿里云CDN HTTPS技术专家金九,分享Tengine的一些HTTPS实践经验.内容主要有四个方面:HTTPS趋势.HTTPS基础.HTTPS实践.HTTPS调试. 一.HTTPS趋势 这一 ...

  9. nginx Server names

    通配符名称 正則表達式名称 混合名称 优化 兼容性 server名称定义使用的server_name指令和决定哪个server块用于一个给定的请求. 參见"怎样Nginx处理一个请求&quo ...

随机推荐

  1. python3和python2编码拾遗

    py2编码 tr和unicode str和unicode都是basestring的子类.严格意义上说,str其实是字节串,它是unicode经过编码后的字节组成的序列.对UTF-8编码的str'苑'使 ...

  2. PL/SQL创建用户

    步骤一:新建 步骤二:填写信息 对应SQL代码 -- Create the user create user WENT identified by "longrise" defau ...

  3. HTML --- 简单的标签

    HTML --- 简单的标签 html概述和基本结构 html概述 HTML是 HyperText Mark-up Language 的首字母简写,意思是超文本标记语言,超文本指的是超链接,标记指的是 ...

  4. thinkphp url生成

    为了配合所使用的URL模式,我们需要能够动态的根据当前的URL设置生成对应的URL地址,为此,ThinkPHP内置提供了U方法,用于URL的动态生成,可以确保项目在移植过程中不受环境的影响. 定义规则 ...

  5. 牛客多校第六场 B Shorten IPv6 Address 模拟

    题意: 给你一个二进制表示的IPv6地址,让你把它转换成8组4位的16进制,用冒号分组的表示法.单组的前导0可以省略,连续多组为0的可以用两个冒号替换,但是只允许替换一次.把这个地址通过这几种省略方式 ...

  6. PAT甲级——A1137 Final Grading【25】

    For a student taking the online course "Data Structures" on China University MOOC (http:// ...

  7. 基于IDEA的SSM配置文件整合基础(有步骤)

    今天进行了SSM框架的整合,遇到了很多的错误,但所幸都有解决,以下为基础的整合步骤,后续待完善 1.SSM整合所需要: spring的jar(包含tx).springmvc的jar.mybatis.j ...

  8. python调用scikit-learn机器学习

    不支持深度学习和强化学习 numpy介绍: np.eye(n)生成一个n维单元数组 数据预处理: iris数据加载 from sklearn import datasetsiris = dataset ...

  9. appium + python 自动化调试手机时 UiAutomator exited unexpectedly with code 0, signal null

    放上appium报错图,appium在手机里安装了appium setting 和unlock 软件,输入法也被变成了appium input ,但是就是点不到目标软件,手机也可以被cmd  adb ...

  10. USACO 2012 March Silver Tractor /// 优先队列BFS oj21567

    题目大意: 输入n,(x,y):n为阻挡的草堆数量,(x,y)为开始时拖拉机所在的位置 接下来n行每行一个坐标(a,b):为各个草堆的坐标 输出拖拉机要回到原点(0,0)需要移动的草堆数量 Sampl ...