前一篇文章介绍了nginx的启动初始化过程,包括了所有模块的初始化过程,比如http模块、事件模块等。这里再详细介绍一下http模块的启动过程,还记得在前一篇文章中提到过ngx_conf_parse函数背后隐藏了大量的细节吗?这里就揭开这层神秘的面纱,去看看几个重要的http模块是如何初始化的。这里依然沿用上一篇文章的结构,首先来看几个重要的数据结构。
1. 重要的数据结构
1. ngx_http_module_t
所有http模块都是ngx_http_module_t类型,所有的属性都是回调函数,在http模块初始化过程的不同阶段调用。当一个指令既允许出现在main块(在http{}块内,但是在server{}块外的区域)、server块(在server{}块内,但是在location{}块外的区域)、location块内时,就需要对这些指令进行继承和覆盖的处理,由merge_src_conf和merge_loc_conf完成。
- typedef struct {
- /**
- * 在解析配置文件中http{}配置块前调用
- */
- ngx_int_t (*preconfiguration)(ngx_conf_t *cf);
- /**
- * 在解析配置文件中http{}配置块后调用
- */
- ngx_int_t (*postconfiguration)(ngx_conf_t *cf);
- /**
- * 创建http模块的main config
- */
- void *(*create_main_conf)(ngx_conf_t *cf);
- /**
- * 初始化http模块的main config
- */
- char *(*init_main_conf)(ngx_conf_t *cf, void *conf);
- /**
- * 创建http模块的server config
- */
- void *(*create_srv_conf)(ngx_conf_t *cf);
- /**
- * 合并http模块的server config,用于实现server config到main config的指令的继承、覆盖
- */
- char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
- /**
- * 创建http模块的location config
- */
- void *(*create_loc_conf)(ngx_conf_t *cf);
- /**
- * 合并http模块的location config,用于实现location config到server config的指令的继承、覆盖
- */
- char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
- } ngx_http_module_t;
2. ngx_http_conf_ctx_t
此结构用于保存所有http模块的main、server和location的config结构。可以看到ngx_http_conf_ctx_t的三个属性就是三个数组,数组大小由ngx_http_max_module指定的,这个变量在指令http块的回调函数时初始化。还记得在上一篇介绍的ngx_module_t结构的ctx_index属性吗?对于一个http模块,ctx_index属性就指示了该模块的main、srv和loc的config结构在这三个数组中的下标。
- typedef struct {
- /**
- * 所有模块的main config数组
- */
- void **main_conf;
- /**
- * 所有模块的server config数组
- */
- void **srv_conf;
- /**
- * 所有模块的location config数组
- */
- void **loc_conf;
- } ngx_http_conf_ctx_t;
在处理请求时,可以通过:
- #define ngx_http_get_module_main_conf(r, module) \
- (r)->main_conf[module.ctx_index]
- #define ngx_http_get_module_srv_conf(r, module) (r)->srv_conf[module.ctx_index]
- #define ngx_http_get_module_loc_conf(r, module) (r)->loc_conf[module.ctx_index]
实现起来很简单只要传入的r是ngx_http_request_t类型的,module就是对应的模块,直接通过ctx_index索引就可以获取到对应配置结构。
在解析配置文件过程,通常用在指令的set回调函数中:
- #define ngx_http_conf_get_module_main_conf(cf, module) \
- ((ngx_http_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index]
- #define ngx_http_conf_get_module_srv_conf(cf, module) \
- ((ngx_http_conf_ctx_t *) cf->ctx)->srv_conf[module.ctx_index]
- #define ngx_http_conf_get_module_loc_conf(cf, module) \
- ((ngx_http_conf_ctx_t *) cf->ctx)->loc_conf[module.ctx_index]
2. http模块的启动过程
Events Http
/ \
Server Server
/ \ \
Location Location Location
这棵树中,每个节点对应一个配置块,配置文件的解析就是对这棵树的深度优先遍历,并对每个节点调用ngx_conf_parse函数。其中的Http节点对应的就是ngx_http_module模块,是由它驱动后面的server块和location块的解析,其他的细节部分会在nginx配置文件解析中讲解。下面先来看一下ngx_http_module的结构:
- static ngx_command_t ngx_http_commands[] = {
- { ngx_string("http"),
- NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
- ngx_http_block,
- 0,
- 0,
- NULL },
- ngx_null_command
- };
- static ngx_core_module_t ngx_http_module_ctx = {
- ngx_string("http"),
- NULL,
- NULL
- };
- ngx_module_t ngx_http_module = {
- NGX_MODULE_V1,
- &ngx_http_module_ctx, /* module context */
- ngx_http_commands, /* module directives */
- NGX_CORE_MODULE, /* module type */
- NULL, /* init master */
- NULL, /* init module */
- NULL, /* init process */
- NULL, /* init thread */
- NULL, /* exit thread */
- NULL, /* exit process */
- NULL, /* exit master */
- NGX_MODULE_V1_PADDING
- };
(1) 解析http块1
ngx_http_block在http/ngx_http.c中:
- /* the main http context */
- ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
- if (ctx == NULL) {
- return NGX_CONF_ERROR;
- }
- /**
- * 将传递进来的conf赋值为ctx,即ngx_http_conf_ctx_t类型。
- * 这个conf是ngx_cycle->conf_ctx数组中的元素,而这个元素就是
- * ngx_http_module模块对应的config信息。所以这一步就完成了
- * ngx_http_module模块config信息的初始化。
- */
- *(ngx_http_conf_ctx_t **) conf = ctx;
- 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++;
- }
- /* the http main_conf context, it is the same in the all http contexts */
- 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
- */
- 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
- */
- ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
- if (ctx->loc_conf == NULL) {
- return NGX_CONF_ERROR;
- }
- 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;
- if (module->create_main_conf) {
- ctx->main_conf[mi] = module->create_main_conf(cf);
- if (ctx->main_conf[mi] == NULL) {
- return NGX_CONF_ERROR;
- }
- }
- if (module->create_srv_conf) {
- ctx->srv_conf[mi] = module->create_srv_conf(cf);
- if (ctx->srv_conf[mi] == NULL) {
- return NGX_CONF_ERROR;
- }
- }
- if (module->create_loc_conf) {
- ctx->loc_conf[mi] = module->create_loc_conf(cf);
- if (ctx->loc_conf[mi] == NULL) {
- return NGX_CONF_ERROR;
- }
- }
- }
- /* 先保存cf的副本,待所有http module的指令解析完再恢复 */
- pcf = *cf;
- /* 把解析http module的指令的上下文设置为ngx_http_conf_ctx_t */
- cf->ctx = ctx;
- /* 调用http module的preconfiguration回调函数 */
- for (m = 0; ngx_modules[m]; m++) {
- if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
- continue;
- }
- module = ngx_modules[m]->ctx;
- if (module->preconfiguration) {
- if (module->preconfiguration(cf) != NGX_OK) {
- return NGX_CONF_ERROR;
- }
- }
- }
- /* parse inside the http{} block */
- cf->module_type = NGX_HTTP_MODULE; // 只解析NGX_HTTP_MODULE模块的指令,即http module的指令
- cf->cmd_type = NGX_HTTP_MAIN_CONF; // 只解析NGX_HTTP_MAIN_CONF类型的指令
- /* 只有符合module_type和cmd_type的指令才会被解析 */
- rv = ngx_conf_parse(cf, NULL);
- typedef struct {
- /**
- * 存储所有的ngx_http_core_srv_conf_t,元素的个数等于server块的个数。
- */
- ngx_array_t servers;
- /**
- * 包含所有phase,以及注册的phase handler,这些handler在处理http请求时,
- * 会被依次调用,通过ngx_http_phase_handler_t的next字段串联起来组成一个
- * 链表。
- */
- ngx_http_phase_engine_t phase_engine;
- /**
- * 以hash存储的所有request header
- */
- ngx_hash_t headers_in_hash;
- /**
- * 被索引的nginx变量 ,比如通过rewrite模块的set指令设置的变量,会在这个hash
- * 中分配空间,而诸如$http_XXX和$cookie_XXX等内建变量不会在此分配空间。
- */
- ngx_hash_t variables_hash;
- /**
- * ngx_http_variable_t类型的数组,所有被索引的nginx变量被存储在这个数组中。
- * ngx_http_variable_t结构中有属性index,是该变量在这个数组的下标。
- */
- ngx_array_t variables;
- ngx_uint_t ncaptures;
- /**
- * server names的hash表的允许的最大bucket数量,默认值是512。
- */
- ngx_uint_t server_names_hash_max_size;
- /**
- * server names的hash表中每个桶允许占用的最大空间,默认值是ngx_cacheline_size
- */
- ngx_uint_t server_names_hash_bucket_size;
- /**
- * variables的hash表的允许的最大bucket数量,默认值是512。
- */
- ngx_uint_t variables_hash_max_size;
- /**
- * variables的hash表中每个桶允许占用的最大空间,默认值是64.
- */
- ngx_uint_t variables_hash_bucket_size;
- /**
- * 主要用于初始化variables_hash变量。以hash方式存储所有的变量名,同时还保存
- * 变量名和变量内容的kv对的数组,ngx_hash_t就是以这个数组进行初始化的。这个
- * 变量时临时的, 只在解析配置文件时有效,在初始化variables_hash后,会被置为NULL。
- */
- ngx_hash_keys_arrays_t *variables_keys;
- /**
- * 监听的所有端口,ngx_http_port_t类型,其中包含socket地址信息。
- */
- ngx_array_t *ports;
- ngx_uint_t try_files; /* unsigned try_files:1 */
- /**
- * 所有的phase的数组,其中每个元素是该phase上注册的handler的数组。
- */
- ngx_http_phase_t phases[NGX_HTTP_LOG_PHASE + 1];
- } ngx_http_core_main_conf_t;
可以看到这个配置结构包含很多重要的信息,比如nginx变量、phase handler数组等等。在ngx_http_core_module定义了大量的配置指令,它们都是在http{}块中出现的,其中包括两个重要的指令:
- { ngx_string("server"),
- NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_MULTI|NGX_CONF_NOARGS,
- ngx_http_core_server,
- 0,
- 0,
- NULL },
- { ngx_string("location"),
- NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12,
- ngx_http_core_location,
- NGX_HTTP_SRV_CONF_OFFSET,
- 0,
- NULL },
(2) 解析server块1
- ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
- if (ctx == NULL) {
- return NGX_CONF_ERROR;
- }
- http_ctx = cf->ctx; // 全局http module的配置
- ctx->main_conf = http_ctx->main_conf;
- /* the server{}'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 server{}'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;
- }
- 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);
- 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);
- if (mconf == NULL) {
- return NGX_CONF_ERROR;
- }
- ctx->loc_conf[ngx_modules[i]->ctx_index] = mconf;
- }
- }
- cscf = ctx->srv_conf[ngx_http_core_module.ctx_index];
- cscf->ctx = ctx;
- cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
- cscfp = ngx_array_push(&cmcf->servers);
- if (cscfp == NULL) {
- return NGX_CONF_ERROR;
- }
- *cscfp = cscf;
在上面介绍ngx_http_core_module的main config时,有一个servers数组用于存储所有server块的配置结构,这段代码就是完成将正在解析的server块的配置信息push到这个数组中。
- /* parse inside server{} */
- pcf = *cf; // 保存cf的副本,在解析完server块后恢复
- cf->ctx = ctx;
- cf->cmd_type = NGX_HTTP_SRV_CONF; // 解析NGX_HTTP_SRV_CONF类型的指令,即server块下的指令
为解析server块做准备,可以看到这里直接洗NGX_HTTP_SRV_CONF类型的指令,也就是server块下的指令。接下来就看一下location块的解析,它指令回调函数是ngx_http_core_location。
(3) 解析location块
- ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
- if (ctx == NULL) {
- return NGX_CONF_ERROR;
- }
- pctx = cf->ctx;
- ctx->main_conf = pctx->main_conf;
- ctx->srv_conf = pctx->srv_conf;
- ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
- if (ctx->loc_conf == NULL) {
- return NGX_CONF_ERROR;
- }
- for (i = 0; ngx_modules[i]; i++) {
- if (ngx_modules[i]->type != NGX_HTTP_MODULE) {
- continue;
- }
- module = ngx_modules[i]->ctx;
- if (module->create_loc_conf) {
- ctx->loc_conf[ngx_modules[i]->ctx_index] =
- module->create_loc_conf(cf);
- if (ctx->loc_conf[ngx_modules[i]->ctx_index] == NULL) {
- return NGX_CONF_ERROR;
- }
- }
- }
- clcf = ctx->loc_conf[ngx_http_core_module.ctx_index];
- clcf->loc_conf = ctx->loc_conf;
- value = cf->args->elts;
- /* 分析location的匹配规则 */
- if (cf->args->nelts == 3) { /* 包括location在内有3个参数 */
- len = value[1].len;
- mod = value[1].data;
- name = &value[2];
- if (len == 1 && mod[0] == '=') {
- /**
- * 字符串精确匹配
- * location = /hello { ...
- */
- clcf->name = *name;
- clcf->exact_match = 1;
- } else if (len == 2 && mod[0] == '^' && mod[1] == '~') {
- /**
- * 前缀匹配
- * location ^~ /hello/ { ...
- */
- clcf->name = *name;
- clcf->noregex = 1;
- } else if (len == 1 && mod[0] == '~') {
- /**
- * 区分大小写正则匹配
- */
- if (ngx_http_core_regex_location(cf, clcf, name, 0) != NGX_OK) {
- return NGX_CONF_ERROR;
- }
- } else if (len == 2 && mod[0] == '~' && mod[1] == '*') {
- /**
- * 不区分大小写正则匹配
- */
- if (ngx_http_core_regex_location(cf, clcf, name, 1) != NGX_OK) {
- return NGX_CONF_ERROR;
- }
- } else {
- ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
- "invalid location modifier \"%V\"", &value[1]);
- return NGX_CONF_ERROR;
- }
- } else { /* 处理 两个参数的情况,location =/hello {... */
- name = &value[1];
- if (name->data[0] == '=') {
- clcf->name.len = name->len - 1;
- clcf->name.data = name->data + 1;
- clcf->exact_match = 1;
- } else if (name->data[0] == '^' && name->data[1] == '~') {
- clcf->name.len = name->len - 2;
- clcf->name.data = name->data + 2;
- clcf->noregex = 1;
- } else if (name->data[0] == '~') {
- name->len--;
- name->data++;
- if (name->data[0] == '*') {
- name->len--;
- name->data++;
- if (ngx_http_core_regex_location(cf, clcf, name, 1) != NGX_OK) {
- return NGX_CONF_ERROR;
- }
- } else {
- if (ngx_http_core_regex_location(cf, clcf, name, 0) != NGX_OK) {
- return NGX_CONF_ERROR;
- }
- }
- } else {
- clcf->name = *name;
- if (name->data[0] == '@') {
- clcf->named = 1;
- }
- }
- }
- if (ngx_http_add_location(cf, &pclcf->locations, clcf) != NGX_OK) {
- return NGX_CONF_ERROR;
- }
- save = *cf;
- cf->ctx = ctx;
- cf->cmd_type = NGX_HTTP_LOC_CONF; /* 只解析location块内的指令 */
- rv = ngx_conf_parse(cf, NULL);
(4) 解析server块2
在解析server块后,也就是从ngx_conf_parse函数返回,继续执行ngx_http_core_server函数。
- if (rv == NGX_CONF_OK && !cscf->listen) {
- 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);
- #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) {
- return NGX_CONF_ERROR;
- }
- }
(5) 解析http块2
解析完http块后,即从ngx_conf_parse返回,下面看一下ngx_http_block后续的一些初始化工作。
- cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
- cscfp = cmcf->servers.elts;
- 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]);
- if (rv != NGX_CONF_OK) {
- goto failed;
- }
- }
- rv = ngx_http_merge_servers(cf, cmcf, module, mi);
- if (rv != NGX_CONF_OK) {
- goto failed;
- }
- }
- 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) {5
- 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) {
- return NGX_CONF_ERROR;
- }
- if (ngx_http_init_headers_in_hash(cf, cmcf) != NGX_OK) {
- return NGX_CONF_ERROR;
- }
- 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;
- }
- cf = pcf;
- if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) {
- return NGX_CONF_ERROR;
- }
- /* optimize the lists of ports, addresses and server names */
- if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) {
- return NGX_CONF_ERROR;
- }
上面就是http块初始化的全过程,这里主要列出server块和location块的初始化。下一篇详细介绍nginx配置文件的解析。