卷确 2019-06-28
运营研发团队 李乐
配置文件是nginx的基础,对于学习nginx源码甚至开发nginx模块的同学来说更是必须深究。本文将从源码从此深入分析nginx配置文件的解析,配置存储,与配置查找。
看本文之前读者可以先思考两个问题:
//获取限流相关配置 lrcf = ngx_http_get_module_loc_conf(r, ngx_http_limit_req_module); //获取fastcgi相关配置 flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module);
为什么可以这样获取到限流和fastcgi相关的配置呢?
相信学习本文之后,这两个问题将不在话下。
在学习nginx配置文件的解析过程之前,需要先了解一下nginx模块与指令的一些基本知识。
nginx的配置指令可以分为两大类:指令块(如events、http、server和location)与单条指令(如worker_processes、root、rewrite等)。
nginx规定指令块可以嵌套(如http块中可以嵌套server指令,server块中可以嵌套location指令),指令可以同时出现在不同的指令块(如root指令可以同时出现在http、server和location指令块)。
配置文件这种层次的复杂性,导致配置文件的解析与存储等的复杂性。
结构体ngx_module_t用于定义一个nginx模块,这里需要重点关注以下几个字段。
struct ngx_module_s { ngx_uint_t ctx_index; //用于给同类型的模块编号 ngx_uint_t index; //用于给所有模块编号 void *ctx; //模块上下文;很重要;不同类型的模块通常指向不同类型的结构体,结构体通常包含若干函数指针 ngx_command_t *commands; //指令数组 ngx_uint_t type; //模块类型编码
type字段表示模块类型编码。ctx指向模块上下文结构体,且不同类型的模块通常指向不同类型的结构体,该结构体中通常会包含若干函数指针。
nginx常用模块可以分为这么几类:核心模块,事件模块语http模块(conf类模块与mail类模块暂不考虑)。见下表
从上表列出的三种类型的模块上下文结构体可以看出:
而http模块上下文结构的preconfiguration和postconfiguration用于初始化http处理流程相关操作。
index字段用于给所有模块编号,比如:
ngx_max_module = 0; for (i = 0; ngx_modules[i]; i++) { ngx_modules[i]->index = ngx_max_module++; } ctx_index用于给同类型的模块编号,比如: ngx_http_max_module = 0; for (m = 0; ngx_modules[m]; m++) { if (ngx_modules[m]->type != NGX_HTTP_MODULE) { continue; } ngx_modules[m]->ctx_index = ngx_http_max_module++; }
nginx的各个模块组合形成了其强大的处理能力,而每个模块只实现一个特定的功能。比如限流功能由模块ngx_http_limit_conn_module或者模块实现ngx_http_limit_req_module;fastcgi转发功能由模块ngx_http_fastcgi_module
实现;proxy转发功能由ngx_http_proxy_module(当然转发功能的实现还必须有模块ngx_http_upstream_module)。
当我们配置了指令proxy_pass或者fastcgi_pass时,该指令应该由哪个模块来解析呢?显然应该由实现此功能的模块来解析。即nginx配置文件的解析是分散到各个模块的。
每个模块都有一个commands数组,存储该模块可以解析的所有配置指令。指令结构体由ngx_command_t定义:
struct ngx_command_s { ngx_str_t name; ngx_uint_t type; char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); ngx_uint_t conf; ngx_uint_t offset; void *post; };
用于校验参数数目。常用指令类型如下:
下面这张图展示了指令的基本分类(通过颜色区分,各种颜色的文字描述指令类型以及该指令只能被哪种类型的模块解析):
http配置相对复杂,h指令块嵌套,模块众多,导致http配置解析与存储的复杂性。因此本小节重点讲述http相关配置存储的方案设计。
前面提到每个模块负责解析和存储自己关心的配置指令,即每个模块都应该有个可以存储配置的结构体,该结构体通过模块上下文结构体的函数create_conf,create_main_conf,create_srv_conf或者create_loc_conf创建。
比如说如下表:
问题来了,每个模块创建自己的配置结构体,存储是完全分散的,如何能快速查找到这些配置结构体呢?
最容易想到的就是声明一个void*的数组,数组元素数目就是模块数目,以模块的index字段作为数组的索引,数组的每个元素都指向对应模块的配置结构体。
但是不要忘记,nginx配置文件是有层次结构的,如http指令块中可以声明多个单条指令和多个server指令块,server指令块中可以声明多个单条指令和多个location指令块,location配置又可以声明多个单条指令。
我们可以这样来设计:
http指令块内包含的所有指令或者指令块只能被http模块(NGX_HTTP_MODULE)解析。
参考这四点设计,我们可以简单画出http配置存储结构示意图:
这个结构似乎是可以的,但是我们忘记了一件事:一些指令可以同时出现在http指令块、server指令块和location指令块。
即http块中的指令类型可以是NGX_HTTP_MAIN_CONF,也可以是NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF,还可以是NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONFNGX_HTTP_LOC_CONF;
而server块中的指令类型可以是NGX_HTTP_SRV_CONF,也可以是NGX_HTTP_SRV_CONFNGX_HTTP_LOC_CONF。(位或运算表示同时属于多种类型)
比如说指令root的类型位NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF,此时该配置应该存储在loc_conf配置结构,但是其可能会配置在http指令块中、server指令块或者location指令块。
为此我们修改上面的结构如下:
上面我们分析了http指令块内部的所有指令可能的存储格式,events指令块内部存储格式相比较简单很多,读者可以试着画一画。
那么这是否是nginx采用的存储格式呢?可以说和上图非常类似,nginx设计的配置存储格式见下图,这里暂时留两个疑问:
本文作为nginx配置文件解析的第一小篇,简要介绍了nginx模块和指令的基本概念,同时针对http相关配置的存储格式进行了初步设计与讲解,为下文《nginx配置文件解析(二)》讲解。配置文件解析源码分析打下基础。
希望交流,一起学习Nginx PHP Redis 等源码的朋友请入微信群: