很多HTTP模块提供的指令,既可以出现在 http 、server、location、配置块中,当一个指令出现在多个配置块中的时候,有可能值是冲突的,应该以谁的值为准呢?
1. 指令的Context:(指令所能出现的位置)
“指令的Context” 是指 指令所能出现的上下文(配置块位置),例如上图中 “log_format” 这个指令的Context为“http”,则它只能出现在http{ } 配置块内,如果出现在其他配置块中,则Nginx在检查配置文件时就会报错,根本不能启动Nginx。
2. 指令的合并:(多个配置块出现相同指令时的合并策略)
指令分为两种:值指令 和 动作类指令。
值指令 用于存储配置项的值,例如 root、access_log、gzip 等,这类指令是可以合并的;
动作类指令 用于指定行为,例如 rewrite、proxy_pass 等,这类指令是不可以合并的。
存储值的指令继承规则:向上覆盖
存储值的指令的继承规则是:
(1)当 子配置不存在时,直接使用 父配置块;
(2)当 子配置存在时,直接覆盖父配置块。
举例:
server {
listen 8080;
root /home/geek/nginx/html;
access_log logs/geek.access.log main;
location /test {
root /home/geek/nginx/test; #这个location块下的root的值与server{}配置块的值冲突,则直接覆盖父配置块的配置值
access_log logs/access.test.log main;
}
location /dlib {
alias dlib/;
}
location / {
# 在这个location中并没有指定root指令的值,则直接继承父配置块server{} 在的root配置
}
}
3. HTTP模块合并配置的实现:(从源码的层面分析指令的冲突覆盖策略)
有些第三方模块在实现时并没有严格遵循Nginx官方的指令冲突覆盖规则,又没有详细的说明文档,这是只能通过源码来判断某个具体的指令在哪个配置块下生效,判断依据有以下几种方法:
(1)指令允许出现在哪些块下?ngx_command_t
一个模块提供的所有配置指令全部都在 ngx_command_t 结构体构成的数组中可以找到,这个数组中的每个元素就是一条指令,其中 ngx_command_t 结构体中的 “type” 成员所指示的就是本条指令能出现在哪些配置块中:
ngx_command_t 结构体:
typedef struct ngx_command_s ngx_command_t {
ngx_str_t name; //指令的名字
ngx_uint_t type; //指示指令能出现在哪些配置块中、指令能接受多少个参数
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); //指令解析函数
...
};
以 ngx_http_referer_commands 为例举例说明 type 字段的值:
static ngx_command_t ngx_http_referer_commands[] = {
{
ngx_string("valid_referers"), //指令名为 valid_referers
NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_1MORE, //指令所能出现的位置:server、location
ngx_http_valid_referers,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
{
},
...
};
Nginx中 标志指令出现位置的宏:
//常用的三个:
#define NGX_HTTP_MAIN_CONF 0x02000000 //http { }
#define NGX_HTTP_SRV_CONF 0x04000000 //server { }
#define NGX_HTTP_LOC_CONF 0x08000000 //location { }
//下面几个不是很常用:
#define NGX_HTTP_UPS_CONF 0x10000000 //upstream { }
#define NGX_HTTP_SIF_CONF 0x20000000
#define NGX_HTTP_LIF_CONF 0x40000000
#define NGX_HTTP_LMT_CONF 0x80000000
其中,type字段所取的另一个宏值 “NGX_CONF_1MORE” 表示本指令所能接受的参数个数,最多为8个:
//nginx指令可以接受的参数数量:
#define NGX_CONF_NOARGS 0x00000001
#define NGX_CONF_TAKE1 0x00000002
#define NGX_CONF_TAKE2 0x00000004
#define NGX_CONF_TAKE3 0x00000008
#define NGX_CONF_TAKE4 0x00000010
#define NGX_CONF_TAKE5 0x00000020
#define NGX_CONF_TAKE6 0x00000040
#define NGX_CONF_TAKE7 0x00000080
//表示最多只能接受8个参数
#define NGX_CONF_MAX_ARGS 8
#define NGX_CONF_1MORE 0x00000800 //可以携带 1个 或者 多个 参数
#define NGX_CONF_2MORE 0x00001000
(2)不同配置块间合并指令:ngx_http_module_t
如果多个配置块中都出现了相同的指令,如何判断某个配置块中的这条指令的取值:
ngx_module_t 结构体表示一个模块:
typedef struct ngx_module_s ngx_module_t;
struct ngx_module_s {
void *ctx; //指向不同类型的模块实例
};
当此模块表示的是一个HTTP类型的模块,则 ngx_module_t->ctx 取值为 &ngx_http_module_t 。
ngx_http_module_t 中定义了 8个回调方法,用于 配置解析阶段 被框架代码调用:
//注意这8个回调方法的调用顺序并不是按照它们在结构体中的排列顺序
typedef struct {
ngx_int_t (*preconfiguration)(ngx_conf_t *cf); //创建结构体后,开始解析之前调用。 常用于添加变量定义
ngx_int_t (*postconfiguration)(ngx_conf_t *cf); //解析、合并完配置后调用
void *(create_main_conf)(ngx_conf_t *cf); //创建模块的main配置
char *(*init_main_conf)(ngx_conf_t *cf, void *conf); //初始化模块的main配置
void *(*create_srv_conf)(ngx_conf_t *cf); //创建模块的server配置
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf); //初始化模块的server配置
void *(*create_loc_conf)(ngx_conf_t *cf); //创建模块的location配置
char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf); //初始化模块的location配置
} ngx_http_module_t;
所以,如果一个指令在 location 配置块内生效,那么它一定会定义 *(*create_loc_conf)
和 *(*merge_loc_conf)
回调函数,用于创建模块的location配置,并将http和server配置块的配置合并到location配置块内。
以 ngx_http_referer_module 为例:
static ngx_http_module_t ngx_http_referer_module_ctx = {
ngx_http_referer_add_variable,
NULL,
NULL,
NULL,
NULL,
NULL,
ngx_http_referer_create_conf, //create_loc_conf
ngx_http_referer_merge_conf //merge_loc_conf
};
static char *ngx_http_referer_merge_conf(ngx_conf_t *cf, void *parent, void *child) {
//parent就是父模块,如http、server配置块;child就是本配置块,即location配置块
//从函数的实现方法中就可以看到此模块关于配置项的冲突配置策略
}