Nginx提供完善的配置文件。本篇文章主要讲一下针对HTTP模块,Nginx是如何解析配置文件并且管理配置项的。
配置文件举例
下面举个例子:
http {
test_str main;
server{
listen 80;
test_str server80;
location /url1{
mytest;
test_str loc1;
}
location /url2{
mytest;
test_str loc2;
}
}
server{
listen 8080;
test_str server8080;
location /url3{
mytest;
test_str loc3;
}
}
}
可以看到,HTTP模块的配置文件分为三种配置块:http{}、server{}、location{},http{}配置块中可以包含多个server{}配置块,server{}配置块中可以包含多个location{}配置块,而location配置块中又可以互相嵌套,听起来很复杂,但是Nginx通过结构相同的“上下文”,实现起来并不复杂。
上面这个配置文件中类似test_str和mytest称为配置项,它们的值叫做配置项参数。
说一下比较容易混淆的概念:与XXX配置块相关的配置项以及XXX级别的配置项(XXX表示http、server、或location)。
XXX级别的配置项是说包含这个配置项的配置块的最小级别。如上面这个配置文件中,参数值为main的test_str为main级别的配置项,参数值为server80的test_str配置项为sever级别,参数值为loc2的test_str为location级别的配置项。与XXX相关的配置项要使用create_XXX_conf函数生成对应的配置结构体,这个在下面会提到。
与XXX配置块相关的配置项是说这个配置块与XXX配置块相关,但是不一定属于XXX级别。与哪个配置块相关这个是由ngx_command_t结构体决定,暂且不提。上面的配置文件中的test_str是location级别,但是它可以是任意级别的配置项。
数据结构定义
Nginx中的模块是ngx_module_t结构体,它的定义如下:
struct ngx_module_s {
//当前模块在该类别中的序号
ngx_uint_t ctx_index;
//当前模块在Ngx_modules数组中的序号
ngx_uint_t index;
//spare系列的保留变量,暂时未使用
ngx_uint_t spare0;
ngx_uint_t spare1;
ngx_uint_t spare2;
ngx_uint_t spare3;
//模块的版本,便于将来扩展,目前只有一种,默认为1
ngx_uint_t version;
//ctx用于指向一类模块的上下文结构体
//ctx将会指向特定类型模块的公共接口
//例如在Http模块中,ctx指向Ngx_http_module_t
void *ctx;
//处理nginx.conf的配置项
ngx_command_t *commands;
//模块类型
//NGX_HTTP_MODULE NGX_CORE_MODULE
//NGX_CONF_MODULE NGX_EVENT_MODULE
//NGX_MAIL_MODULE 当然也可以自定义模块
ngx_uint_t type;
/*在Ngx的启动、停止过程中,以下7个函数指针表示有7个执行点
会分别调用这7种方法。对于任何一个方法而言,如果不需要Ngx
在某个时刻执行它,可以简单地定义为NULL*/
//在master进程启动时调用
ngx_int_t (*init_master)(ngx_log_t *log);
//在初始化所有模块时调用,在master/worker模式下这个阶段将在
//启动worker子进程前完成
ngx_int_t (*init_module)(ngx_cycle_t *cycle);
//在正常服务前调用此回调函数,在master/worker模式下,多个
//worker子进程已经产生,在每个worker进程的初始化过程会
//调用所有模块次回调函数
ngx_int_t (*init_process)(ngx_cycle_t *cycle);
//由于Ngx暂不支持多线程模式,所以此函数没被调用过,设为NULL
ngx_int_t (*init_thread)(ngx_cycle_t *cycle);
void (*exit_thread)(ngx_cycle_t *cycle);
//会在服务停止前调用此回调函数。在master/worker模式下
//worker进行会在退出前调用它
void (*exit_process)(ngx_cycle_t *cycle);
//将在master进程退出前调用
void (*exit_master)(ngx_cycle_t *cycle);
//暂时没有用到的8个保留字段
uintptr_t spare_hook0;
uintptr_t spare_hook1;
uintptr_t spare_hook2;
uintptr_t spare_hook3;
uintptr_t spare_hook4;
uintptr_t spare_hook5;
uintptr_t spare_hook6;
uintptr_t spare_hook7;
};
HTTP模块没有与进程相关的处理,所以并没有实现上面的几个回调函数,但是在上一篇关于事件模块ngx_event_core_module中,实现了init_module和init_process函数。其中*ctx成员指向的是每类模块的公共接口,对于HTTP模块来说,它们都需要实现公共接口ngx_http_module_t,下面看一下ngx_http_module_t结构体的定义:
typedef struct {
//解析配置文件前调用
ngx_int_t (*preconfiguration)(ngx_conf_t *cf);
//完成配置文件的解析后调用
ngx_int_t (*postconfiguration)(ngx_conf_t *cf);
//当需要创建数据结构用于存储Main相关的配置项时,
//可以通过create_main_conf回调方法创建存储与Main相关的配置项的结构体
void *(*create_main_conf)(ngx_conf_t *cf);
//常用于初始化main级别配置项
char *(*init_main_conf)(ngx_conf_t *cf, void *conf);
//当需要创建数据结构用于存储与srv相关的配置项时
//可以通过实现create_srv_conf回调方法生成存储srv相关配置项的结构体
void *(*create_srv_conf)(ngx_conf_t *cf);
//此回调函数主要用于合并main级别和srv级别的同名配置项
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
//当需要创建数据结构用于存储与loc相关的配置项时,可以实现本回调函数
void *(*create_loc_conf)(ngx_conf_t *cf);
//用于合并srv级别和loc级别下的同名配置项
char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;
上面这个接口中的回调函数实际的调用顺序与定义的顺序并不一致。具体调用顺序会在下面再说明。create_XXX_conf的函数用来生成与XXX相关的配置结构体。
ngx_module_t中的commands成员也是很重要,它代表着一个ngx_command_t的数组。ngx_command_t结构体是配置项参数真正解析的地方,看一下它的数据结构定义:
struct ngx_command_s {
//配置项名称
ngx_str_t name;
//配置项类型,type将指定配置项可以出现的位置。例如出现在server{}或location{}
//中,以及它可以携带的参数个数
ngx_uint_t type;
//出现了name中指定的配置项后,将会调用set方法处理配置项的参数
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
//在配置上下文结构体中的偏移量
ngx_uint_t conf;
//通常用于使用预设的解析方法解析配置项
ngx_uint_t offset;
//配置项读取后的处理方法
void *post;
};
这个数据结构定义得非常巧妙。我们知道C语言没有C++或者Java提供那种类似模板的机制,那它如何实现类型的通用性?在ngx_http_module_t中,create_XXX_conf函数创建了各种类型的配置结构体。而ngx_command_t通过结构体地址以及成员的偏移量实现通用性:conf决定了配置上下文结构体中的偏移量(配置上下文结构体会在下文中说明,conf其实同时也决定了配置项与什么配置块相关),从而通过上下文结构体可以定位到属于当前模块的配置结构体的地址,然后offset决定了配置结构体中存储配置项参数的成员的偏移,这样既可以知道配置结构体的地址,又知道了成员相对于结构体的地址,自然可以实现通用性。
set函数是在配置文件中具体解析配置项参数的函数,其中cf中存储了当前所在的上下文结构体,再通过cmd中定义的偏移量,conf指针指向的是配置结构体的指针。我们看一下set函数是如何调用的:(截取于ngx_conf_handler@ngx_conf_file.c)
//cmd->conf可以是srv_conf成员相对于ngx_http_conf_ctx_t结构体的偏移
confp = *(void **) ((char *) cf->ctx + cmd->conf);
if (confp) {
//相当于返回了ngx_http_conf_ctx_t中第几个配置结构体
conf = confp[ngx_modules[i]->ctx_index];
}
上文多次提到的配置上下文结构体是很关键的结构体,每一个配置块,不管是http{}、server{}、location{}都有属于自己的配置上下文结构体:ngx_http_conf_ctx_t,它有三个指针,分别指与不同配置块相关的结构体指针数组,数组与HTTP模块的标号ctx_index一一对应,方便查找:
typedef struct {
void **main_conf;
void **srv_conf;
void **loc_conf;
} ngx_http_conf_ctx_t;