Nginx源码分析HTTP框架加载

转载请注明出处:http://www.firefoxbug.com/?p=2064

前文描述了Nginx核心模块的加载,对于Nginx模块的加载是根据配置文件读取从而递归加载的。本文以HTTP框架为例,解释Nginx的HTTP模块加载以及下面的子模块如何加载注册。
Nginx配置文件结构如下

http{
	server{
		location{
		...		
		}
		location{

		}
		...	
	}
	server{
		...
	}
	...
}

1.直属于http{}块内的配置项称为main级别配置项
2.直属于server{}块内的配置项称为srv级别配置项
3.直属于location{}块内的配置项称为loc级别配置项

HTTP模块的模块类型是NGX_HTTP_MODULE,主要就是加载http{}内的模块,模块上下文是ngx_http_conf_ctx_t。

typedef struct {
	void        **main_conf;//数组,数组成员是void*
	void        **srv_conf;
	void        **loc_conf;
} ngx_http_conf_ctx_t;

ngx_http_conf_ctx_t每个成员都是一个二级指针数组,分别指向不同块的配置,Nginx解析配置http{}块就会初始化这样一个结构体,同样的解析到server{}块也会生成,还有location{}块。这里这样做的原因主要是有些配置项可以出现在不同的块内,比如access_log这种,到最后需要有一个合并的过程(比较复杂)。
HTTP模块接口是ngx_http_module_t。
//HTTP框架在读取,重载配置文件时定义了由ngx_http_module_t接口描述的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); //当需要创建数据结构用户存储srv级别的全局配置项时候调用
	char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf); //srv覆盖策略

	void       *(*create_loc_conf)(ngx_conf_t *cf); //当需要创建数据结构用户存储loc级别的全局配置项时候调用
	char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf); //loc覆盖策略
} ngx_http_module_t;

ngx_conf_parse里面的handler会调用ngx_http_block,下面详细介绍Nginx HTTP框架各个模块的加载
src/http/ngx_http.c -->> ngx_http_block
1.清点所有HTTP模块,统计数量,分配相应的结构体内存

	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++;
	}

	//创建HTTP对应的conf,因为每个级别(main/ser/loc)都会包含模块的conf
	ctx->main_conf = ngx_pcalloc(cf->pool,
								 sizeof(void *) * ngx_http_max_module)

	ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);

	ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);

	//调用对应的create_xxx_conf回调函数

2.开始调用HTTP模块接口ngx_http_module_t的回调函数create_main_conf(创建数据结构)
//开始遍历
for (m = 0; ngx_modules[m]; m++) {
	if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
		continue;
	}
	
	//得到对应的module上下文
	module = ngx_modules[m]->ctx;
	//得到对应的索引 
	mi = ngx_modules[m]->ctx_index;
	
	//如果有对应的回调,则调用回调函数,然后将返回的模块config设置到ctx的对应的conf列表中
	if (module->create_main_conf) {
		ctx->main_conf[mi] = module->create_main_conf(cf);
	}

	if (module->create_srv_conf) {
		ctx->srv_conf[mi] = module->create_srv_conf(cf);
	}

	if (module->create_loc_conf) {
		ctx->loc_conf[mi] = module->create_loc_conf(cf);
	}
}

3.开始调用HTTP模块接口ngx_http_module_t的回调函数prestconfiguration(解析配置文件前调用)
for (m = 0; ngx_modules[m]; m++) {
	if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
		continue;
	}

	module = ngx_modules[m]->ctx;
	
	//如果存在prestconfiguratio则调用初始化,真正初始化模块之前需要调用preconfiguration来进行一些操作。
	if (module->preconfiguration) {
		if (module->preconfiguration(cf) != NGX_OK) {
			return NGX_CONF_ERROR;
		}
	}
}

4.开始解析读取nginx配置文件下所有HTTP模块
cf->module_type = NGX_HTTP_MODULE;
cf->cmd_type = NGX_HTTP_MAIN_CONF;
//继续parse config,这里注意传递进去的文件名是空
rv = ngx_conf_parse(cf, NULL);

5.当http block完全parse完毕之后,就需要merge(main和srv或者srv和loc)相关的config了。不过在每次merge之前都会调用HTTP模块接口ngx_http_module_t的init_main_conf(初始化main conf)。
for (m = 0; ngx_modules[m]; m++) {
	if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
		continue;
	}

	//首先取得模块以及对应索引
	module = ngx_modules[m]->ctx;
	mi = ngx_modules[m]->ctx_index;

	/* init http{} main_conf's */

	//如果有init_main_conf,则首先初始化main conf
	if (module->init_main_conf) {
		rv = module->init_main_conf(cf, ctx->main_conf[mi]);
		if (rv != NGX_CONF_OK) {
			goto failed;
		}
	}

	//然后开始merge config
	rv = ngx_http_merge_servers(cf, cmcf, module, mi);
	if (rv != NGX_CONF_OK) {
		goto failed;
	}
}

6.当merge完毕之后,然后就是初始化location tree,创建handler phase,调用HTTP模块接口ngx_http_module_t的postconfiguration(完成配置文件解析后调用),以及变量的初始化。
for (s = 0; s < cmcf->servers.nelts; s++) {

	clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];

	if (ngx_http_init_locations(cf, cscfp[s], clcf) != NGX_OK) {
		return NGX_CONF_ERROR;
	}

	if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) {
		return NGX_CONF_ERROR;
	}
}

//初始化handler phase 
if (ngx_http_init_phases(cf, cmcf) != NGX_OK) {
	return NGX_CONF_ERROR;
}

if (ngx_http_init_headers_in_hash(cf, cmcf) != NGX_OK) {
	return NGX_CONF_ERROR;
}

//遍历模块,然后调用对应的postconfiguration
for (m = 0; ngx_modules[m]; m++) {
	if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
		continue;
	}

	module = ngx_modules[m]->ctx;
	
	//调用回调
	if (module->postconfiguration) {
		if (module->postconfiguration(cf) != NGX_OK) {
			return NGX_CONF_ERROR;
		}
	}
}

//开始初始化变量
if (ngx_http_variables_init_vars(cf) != NGX_OK) {
	return NGX_CONF_ERROR;
}

7.注册handler phase以及socket相关初始化,HTTP处理请求是分阶段的,后面的文章再详细介绍。
if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) {
	return NGX_CONF_ERROR;
}


/* optimize the lists of ports, addresses and server names */
//初始化socket相关的东西
if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) {
	return NGX_CONF_ERROR;
}

总结:由上面的HTTP框架加载过程可以看出HTTP对于每个模块定义ngx_http_module_t的调用顺序是:

//创建三个级别的配置结构体数组指针
create_main_conf
create_srv_conf
create_loc_conf

//读取配置文件前准备
preconfiguration
//这中间过程就是读取配置文件

//初始化main_conf
init_main_conf

//各个级别配置合并
merge_srv_conf
merge_loc_conf

//读取配置文件之后
postconfiguration

标签:C/C++, Linux, Nginx

评论已关闭