刚开始我们要介绍一下http模块的定义:
typedef struct {
ngx_int_t (*preconfiguration)(ngx_conf_t *cf); //在解析配置文件http{}之前调用
ngx_int_t (*postconfiguration)(ngx_conf_t *cf); //在解析配置文件http{}之前调用
void *(*create_main_conf)(ngx_conf_t *cf); //创建http模块的main config
char *(*init_main_conf)(ngx_conf_t *cf, void *conf); //初始化main config的时候调用
void *(*create_srv_conf)(ngx_conf_t *cf); //创建sev config时候调用
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);//合并http模块的server config,用于实现server config到main config的指令的继承、覆盖
void *(*create_loc_conf)(ngx_conf_t *cf); //创建loc config的时候调用
char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf); //合并http模块的location config,用于实现location config到server config的指令的继承、覆盖
} ngx_http_module_t;
前面一篇文章已经把http部分的配置结构讲的差不多了,这篇文章开始真正开始讲初始化了。我们已经知道http的初始化是从ngx_http_module模块的http命令的set回调函数ngx_http_block开始的,接下来就来分析这个函数吧:
//为http模块所有的配置分配内存空间
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}
//因为传进来的是ngx_http_module模块在全局变量cycle的ctx域中index处的指针,所以这句代码相当于是在cycle中为ngx_http_module的配置赋值,相当于是为http部分的配置占一个坑
*(ngx_http_conf_ctx_t **) conf = ctx;
嗯,这段代码应该比较容易理解了,基本与event模块的初始化一个调调。说白了就是创建配置结构的指针,然后还要在cycle的ctx里面为ngx_http_module占一个坑。
//初始化所有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模块初始化分类索引。
//未配置中的每一个项目分配内存空空间(这里是main_conf)
ctx->main_conf = ngx_pcalloc(cf->pool,
sizeof(void *) * ngx_http_max_module);
if (ctx->main_conf == NULL) {
return NGX_CONF_ERROR;
}
/*
* the http null srv_conf context, it is used to merge
* the server{}s' srv_conf's
*/
//这里是srv_conf的指针数组的空间
ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
if (ctx->srv_conf == NULL) {
return NGX_CONF_ERROR;
}
/*
* the http null loc_conf context, it is used to merge
* the server{}s' loc_conf's
*/
//这里是loc_conf的指针数组的空间
ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
if (ctx->loc_conf == NULL) {
return NGX_CONF_ERROR;
}
嗯,上面的代码也很简单,前面的文章已经提到,http模块比较奇葩的,其配置结构共有三种,对应于配置文件的三个地方,因而要为这三个地方都分配内存。
//循环遍历所有的http模块,为他们创建配置
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;
//创建该http模块的main_conf
if (module->create_main_conf) {
ctx->main_conf[mi] = module->create_main_conf(cf);
if (ctx->main_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
//创建该模块的srv_conf
if (module->create_srv_conf) {
ctx->srv_conf[mi] = module->create_srv_conf(cf);
if (ctx->srv_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
//创建该模块的loc_conf
if (module->create_loc_conf) {
ctx->loc_conf[mi] = module->create_loc_conf(cf);
if (ctx->loc_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
}
这段代码也很好理解吧,说白了就是遍历所有的模块,然后找到http类型的模块,分别调用它们创建三种配置类型的函数,并根据它们的分类索引号保存到相应的地方。
/* 先保存cf的副本,待所有http module的指令解析完再恢复 */
//这里之所以要备份,这是因为ngx_conf_parse函数式递归调用了,不能影响外层调用的环境
pcf = *cf;
/* 把解析http module的指令的上下文设置为ngx_http_conf_ctx_t */
//这里之所以要这样做,这是因为在下一次的parse的时候,遇到命令的话,会传进去相应模块的配置,而ngx_conf_handler函数会在cf中寻找相应的模块的配置,然后再调用set回调函数
cf->ctx = ctx;
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
module = ngx_modules[m]->ctx;
/* 调用http module的preconfiguration回调函数 */
if (module->preconfiguration) {
if (module->preconfiguration(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
}
/* parse inside the http{} block */
cf->module_type = NGX_HTTP_MODULE; //这次解析的内容是http模块
cf->cmd_type = NGX_HTTP_MAIN_CONF; //命令的类型,为http命令的main
rv = ngx_conf_parse(cf, NULL); //还是解析http块命令里面的命令
嗯,这段代码也比较容易吧,这里之所以要将cf备份,原因上面也已经说的很明白了,因为是递归调用的,而cf变量的一些部分又是每次调用都共享的,所以里面的肯定不能影响外面的了。然后对cf进行赋值,指明接下来要解析的命令类型,模块类型等。然后还有就是调用所有模块的preconfiguration函数。
好了,这里应该必须要深入http命令内部的命令了,这里就分析两种重要的命令就行了,server与server里面的location命令了。
接下来我们来看看server的回调函数吧,这个函数是ngx_http_core_server,
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t)); //为配置上下文分配内存空间,这里之所以要重新创建新的ngx_http_conf_ctx_t配置结构,是为了处理同时会有好几个server的情况,但是这些创建的ngx_http_conf_ctx_t结构的main_conf都是统一的
if (ctx == NULL) {
return NGX_CONF_ERROR;
}
http_ctx = cf->ctx;
ctx->main_conf = http_ctx->main_conf; //这里用当前刚刚创建的配置上下文的main_conf指向以前的main_conf,这样可以保证main_conf的统一,因为main_conf只有一个
/* the server{}'s srv_conf */
//创建当前server块的srv_conf的内存空间,因为同一个http里面可能会有多个server命令,因而要为每一个server指令都给所有的http模块进行配置
ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
if (ctx->srv_conf == NULL) {
return NGX_CONF_ERROR;
}
/* the server{}'s loc_conf */
//为当前server指令内的loc_conf分配内存
ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
if (ctx->loc_conf == NULL) {
return NGX_CONF_ERROR;
}
这里首先创建的是ngx_http_conf_ctx_t类型的配置结构,这所以这里要重复创建这个结构,在上面的注释应该已经说的很清楚了,接下来就是为srv_conf以及loc_conf分配内存了。
//还是因为有多个server的存在,所以这里要继续要为每一个模块创建srv_conf与loc_conf
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_HTTP_MODULE) {
continue;
}
module = ngx_modules[i]->ctx;
if (module->create_srv_conf) {
mconf = module->create_srv_conf(cf); //创建http模块的srv_conf结构
if (mconf == NULL) {
return NGX_CONF_ERROR;
}
ctx->srv_conf[ngx_modules[i]->ctx_index] = mconf;
}
if (module->create_loc_conf) {
mconf = module->create_loc_conf(cf); //创建http模块的loc_conf
if (mconf == NULL) {
return NGX_CONF_ERROR;
}
ctx->loc_conf[ngx_modules[i]->ctx_index] = mconf;
}
}
上部分代码就是遍历所有的http模块为他们创建配置了,
cscf = ctx->srv_conf[ngx_http_core_module.ctx_index];
cscf->ctx = ctx; //将当前创建的srv_conf结构的ctx指针指向当前创建的ngx_http_conf_ctx_t结构
cmcf = ctx->main_conf[ngx_http_core_module.ctx_index]; //获取ngx_http_core_module的main_conf,它有一个servers数组,用来保存所有的server配置项,这里要知道的是main_conf 只有一个
cscfp = ngx_array_push(&cmcf->servers); //将刚刚创建的创建的ngx_http_core_module的srv_conf结构,即ngx_http_core_srv_conf_t结构保存入ngx_http_core_module模块的main_conf里面的servers数组当中
if (cscfp == NULL) {
return NGX_CONF_ERROR;
}
*cscfp = cscf; //赋值,这里相当于是真正的压入到数组当中去
嗯,上面的代码需要注意一下,这里首先获得的是在当前创建的ngx_http_core_module模块的srv_conf,接着又获取ngx_http_core_module模块的main_conf,因为mian_conf是统一的,所以可以知道这里获取的其实是在http命令回调函数中创建的main_conf,然后再将刚刚创建的srv_conf结构压入到ngx_http_core_module模块的main_conf的servers域中,它用来保存所有server块的配置。
pcf = *cf; //这里还是备份cf结构,然后准备解析server块命令,这里之所以备份,是因为ngx_conf_parse函数式递归调用了,为了内层的不影响外层的
cf->ctx = ctx; //相当于是把新创建的配置向下文传入解析函数
cf->cmd_type = NGX_HTTP_SRV_CONF; //表示要解析的命令的类型
rv = ngx_conf_parse(cf, NULL); //开始解析server块命令,这里面还涉及到location指令
*cf = pcf;
接下来就可以开始解析server指令快内部的命令了,当然这里面肯定还会涉及到解析location命令。最后解析完成以后还要讲配置文件结构恢复,以免影响外层的解析。至于location指令的解析这里就不讲了,因为说实话,俺没有用过Nginx,所以我都不知道location指令是用来干嘛的。
if (rv == NGX_CONF_OK && !cscf->listen) { //表示当前所属的server块命令并没有用listen指令来申明监听端口,那么nginx会安排一个默认的监听?
ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));
sin = &lsopt.u.sockaddr_in;
sin->sin_family = AF_INET;
#if (NGX_WIN32)
sin->sin_port = htons(80); //默认80端口
#else
sin->sin_port = htons((getuid() == 0) ? 80 : 8000);
#endif
sin->sin_addr.s_addr = INADDR_ANY; //接收任意网卡连接
lsopt.socklen = sizeof(struct sockaddr_in);
lsopt.backlog = NGX_LISTEN_BACKLOG;
lsopt.rcvbuf = -1;
lsopt.sndbuf = -1;
#if (NGX_HAVE_SETFIB)
lsopt.setfib = -1;
#endif
lsopt.wildcard = 1;
(void) ngx_sock_ntop(&lsopt.u.sockaddr, lsopt.addr,
NGX_SOCKADDR_STRLEN, 1);
if (ngx_http_add_listen(cf, cscf, &lsopt) != NGX_OK) { //调用ngx_http_add_listen方法加入监听socket
return NGX_CONF_ERROR;
}
}
嗯,上面的代码是,我们知道一般情况下server块命令里面会有listen命令用来指定http监听端口,这里的代码的意思就是说如果没有listen指令来指定端口的话,那么将会设置一个默认的端口,并调用ngx_http_add_listen函数来创建监听端口。
好了,这样server块命令就解析完了,接下来就可以回到http命令的回调函数了。
/*
* init http{} main_conf's, merge the server{}s' srv_conf's
* and its location{}s' loc_conf's
*/
cmcf = ctx->main_conf[ngx_http_core_module.ctx_index]; //获取ngx_http_core_module模块的main_conf配置
cscfp = cmcf->servers.elts; //获取包含的所有srv_conf结构
//遍历所有的http类型的模块,分别初始化他们的main_conf以及合并那些分散的srv_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 */
if (module->init_main_conf) {
rv = module->init_main_conf(cf, ctx->main_conf[mi]); //根据main_conf的内容进行初始化
if (rv != NGX_CONF_OK) {
goto failed;
}
}
rv = ngx_http_merge_servers(cf, cmcf, module, mi); //合并srv_conf的内容
if (rv != NGX_CONF_OK) {
goto failed;
}
}
首先是初始化所有模块的mian_conf,然后合并那些分散的srv_conf
/* create location trees */
//创建location树
for (s = 0; s < cmcf->servers.nelts; s++) {
clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index]; //首先找到当前server块所创建的ngx_http_conf_ctx_t,然后再在里面找到ngx_http_core_module模块的loc_conf
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;
}
}
if (ngx_http_init_phases(cf, cmcf) != NGX_OK) { //因为nginx把http分化成为11个phase,每一个phase都可以注册handler,ngx_http_core_main_conf_t里面的phases所有的phase handler数组,ngx_http_init_phases函数完成每一个phase的handler的初始化
return NGX_CONF_ERROR;
}
if (ngx_http_init_headers_in_hash(cf, cmcf) != NGX_OK) {// 将http请求的header初始化成hash结构,定义在ngx_http_request.c中的ngx_http_headers_in数组存储了所有的header。这里是根据这个数组去初始化hash结构。
return NGX_CONF_ERROR;
}
//遍历所有的http模块,然后调用他们的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) { //将所有被索引的nginx变量组织成为hash结构
return NGX_CONF_ERROR;
}
/*
* http{}'s cf->ctx was needed while the configuration merging
* and in postconfiguration process
*/
*cf = pcf; //恢复配置文件解析结构
if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) { //初始化phase handler
return NGX_CONF_ERROR;
}
/* optimize the lists of ports, addresses and server names */
if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) { // 这里开始处理监听方面的初始化了,例如将cycle中的ngx_listening_t的handler设置为ngx_http_init_connection函数,嗯,这个我们在处理accept事件的时候提到过,相当于这里与event部分联系上了
return NGX_CONF_ERROR;
}
接下来的代码还是进行一些初始化,最后还要调用ngx_http_optimize_servers来处理http监听,这里就能够与前面讲过的event部分联系上来了。嗯,具体的以后再讲吧。