nginx配置解析流程
上一篇文章分析了nginx配置文件缓冲区的管理,接下来将详细分析nginx是如何解析配置文件的。包含模块上下文结构的创建、core核心模块的解析、event事件模块的解析、http模块的解析。
一、模块上下文结构创建
nginx中的核心模块、事件模块、http模块、赋值均衡模块,反向代理模块等,每一个模块都有一个上下文结构。在解析nginx.conf配置文件时,会将解析出的命令保存到相应模块的上下文结构中。例如:worker_connections 1024;该配置项表示每一个work最大可以处理1024个客户端的连接。nginx在解析到这个配置向时,会将1024这个值保存到事件模块的上下文ngx_event_conf_t中的connections变量中。
nginx有多少个模块,就会创建多少个模块上下文结构。在ngx_init_cycle函数中,会创建所有的模块上下文结构。保存到ngx_cycle_s的conf_ctx变量中,这个变量是一个指针数组。
ngx_cycle_t * ngx_init_cycle(ngx_cycle_t *old_cycle)
{//创建所有模块的配置上下文空间cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));
}
二、core核心模块解析
master进程初始化过程会调用ngx_init_cycle函数初始化ngx_cycle_t结构。这个结构是一个全局结构,每一个进程都有这样一个单独的结构。其中conf_ctx成员是一个指针数组,存放了所有模块的上下文结构。在解析配置文件后,会将解析的结果存储到这个指针数组指向的相应位置。下面代码段将创建一个这样的模块 上下文指针数组。
ngx_cycle_t * ngx_init_cycle(ngx_cycle_t *old_cycle)
{//将每一个核心模块上下文create_conf的返回值存入到conf_ctx中for (i = 0; ngx_modules[i]; i++) {//必须是核心模块,其它模块暂时不解析,留到ngx_conf_parse函数中解析if (ngx_modules[i]->type != NGX_CORE_MODULE) {continue;}//目前只有ngx_core_module模块的上下文实现了ngx_core_module_create_conf方法module = ngx_modules[i]->ctx;if (module->create_conf) {//创建模块的上下文结构rv = module->create_conf(cycle);if (rv == NULL) {ngx_destroy_pool(pool);return NULL;}//将上下文保存到上下文指针数组中相应位置cycle->conf_ctx[ngx_modules[i]->index] = rv;}}
}
目前只有ngx_core_module模块实现了create_conf方法。因此创建核心模块的上下文结构后,内存布局如下:
其中cycle->conf_ctx指针数组下标为0的位置就是ngx_core_module核心模块所在的上下文位置,将该指针指向ngx_core_conf_t结构。而ngx_core_module_create_conf方法目前只是给这个结构的所有成员赋一个初值,真正解析则在ngx_conf_parse函数中进行。 在ngx_conf_parse函数解析core模块的配置项时,会将nginx.conf配置文件中所有核心模块所关心的配置项存放到ngx_core_conf_t上下文结构。以worker_process 4;配置项为例说明core模块的解析流程。
ngx_conf_parse
--->ngx_conf_read_token
首先函数ngx_conf_read_token会一个个检测字符,从而得到worker_processes 4;之后会将worker_processes存放到ngx_conf_s的args数组下标为0位置, 4存储到数组下标为1位置。最后根据worker_processes配置项,在所有模块中查找是否有模块实现了worker_processes配置项命令。如果找到,则调用该命令回调。
ngx_conf_parse
--->ngx_conf_handler
//根据配置项查找所有模块,获取到命令,并执行命令
static ngx_int_t ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
{//执行命令rv = cmd->set(cf, cmd, conf);
}
ngx_core_module核心模块的命令表中,存在worker_processes命令。从而调用命令回调ngx_conf_set_num_slot。
//core模块命令
static ngx_command_t ngx_core_commands[] =
{{ ngx_string("worker_processes"),NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,ngx_conf_set_num_slot,0,offsetof(ngx_core_conf_t, worker_processes),NULL },
}
而函数ngx_conf_set_num_slot将把解析到的配置值保存到ngx_core_conf_t成员变量worker_processes。
char * ngx_conf_set_num_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{char *p = conf;ngx_int_t *np;ngx_str_t *value;ngx_conf_post_t *post;np = (ngx_int_t *) (p + cmd->offset); //找到ngx_core_conf_t成员worker_processes位置value = cf->args->elts; //解析后的命令,在这个例子中value[0] = worker_processes, value[1] = 4
*np = ngx_atoi(value[1].data, value[1].len); //将值保存到ngx_core_conf_t成员worker_processes
return NGX_CONF_OK;
}
其它核心模块的命令解析流程和这个例子基本上一样。读者可以分析下其它core模块命令的实现流程,后续nginx服务器处理客户端请求时,会使用到各种命令。不对命令解析有个了解,到时还真不知道这个命令是做什么的,以及如何解析的。
三、event事件模块解析
经过core核心模块的分析,我们知道,查找到具体的命令后,最终会调用函数执行具体的命令。
//根据配置项查找所有模块,获取到命令,并执行命令
static ngx_int_t ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
{//执行命令rv = cmd->set(cf, cmd, conf);
}
而ngx_events_module事件模块的命令数组中有一个events配置项,命令回调方法为 ngx_event_block
static ngx_command_t ngx_events_commands[] =
{{ ngx_string("events"),NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,ngx_events_block,0,0,NULL },ngx_null_command
};
最终解析到"event {"配置项后,会调用ngx_event_block方法。
char * ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{*ctx = ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *));
}
其中ngx_event_max_module表示有多少个事件模块, 该函数会开辟所有事件模块的上下文空间。
char * ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{//调用各个事件模块的create_conf方法,生成每个事件模块的上下文,存放在指针数组中的相应位置for (i = 0; ngx_modules[i]; i++) {//只解析事件模块,其它模块跳过,暂时不解析 if (ngx_modules[i]->type != NGX_EVENT_MODULE) {continue;}m = ngx_modules[i]->ctx;//创建各个事件模块的上下文结构if (m->create_conf){//将事件模块上下文保存到数组中相应位置(*ctx)[ngx_modules[i]->ctx_index] = m->create_conf(cf->cycle);}}
}
各个事件模块实现的create_conf方法只是创建了上下文结构,然后给成员赋初值,此时并还没有解析具体的事件模块的配置。例如ngx_event_core_module模块的create_conf方法为ngx_event_create_conf,函数将创建ngx_event_conf_t结构,并给成员赋初值。
static void * ngx_event_create_conf(ngx_cycle_t *cycle)
{ngx_event_conf_t *ecf;//创建ngx_event_core_module模块的上下文件结构ecf = ngx_palloc(cycle->pool, sizeof(ngx_event_conf_t));//给各个成员赋初值ecf->connections = NGX_CONF_UNSET_UINT;ecf->use = NGX_CONF_UNSET_UINT;ecf->multi_accept = NGX_CONF_UNSET;ecf->accept_mutex = NGX_CONF_UNSET;ecf->accept_mutex_delay = NGX_CONF_UNSET_MSEC;return ecf;
}
创建完所有事件模块的上下文后,内存布局如下
接下来将开始解析事件模块,函数ngx_events_block会开始调用ngx_conf_parse递归解析。这个时候从解析main块进入解析event块。递归解析完事件块后,会返回继续解析main块。
char * ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{pcf = *cf;cf->ctx = ctx;cf->module_type = NGX_EVENT_MODULE; //指定为事件模块,则只会解析事件模块命令cf->cmd_type = NGX_EVENT_CONF; //递归调用,解析事件块配置rv = ngx_conf_parse(cf, NULL);
}
下图是从解析main块进入解析event的流程,在event块解析完成后,会返回到上一层函数调用,继续解析剩余的main块。
在递归调用进入到event块后,开始解析event事件模块。解析具体event块里的所有命令的流程与解析core核心模块的过程是一样的。这里就不在分析了。
在从解析event块返回后,接下来会对未解析到的event事件命令赋一个初值。
char * ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{//调用各个事件模块的init_conf方法,对各个事件模块未赋值的上下文成员进行初始化for (i = 0; ngx_modules[i]; i++) {//只处理事件模块,其它模块忽略if (ngx_modules[i]->type != NGX_EVENT_MODULE) {continue;}m = ngx_modules[i]->ctx;//给各个模块为解析的成员赋一个默认值if (m->init_conf) {rv = m->init_conf(cf->cycle, (*ctx)[ngx_modules[i]->ctx_index]);}}
}
四、http模块解析
经过core核心模块的分析,我们知道。查找到具体的命令后,最终会调用函数执行具体的命令。
//根据配置项查找所有模块,获取到命令,并执行命令
static ngx_int_t ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
{//执行命令rv = cmd->set(cf, cmd, conf);
}
而ngx_http_module事件模块的命令数组中有一个http配置项,命令回调方法为ngx_http_block。最终解析到"http {"配置项后,会调用ngx_http_block方法。
//开始解析http块
static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{//创建一个http块的ngx_http_conf_ctx_t结构ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));//开辟所有http模块的main_confctx->main_conf = ngx_pcalloc(cf->pool,sizeof(void *) * ngx_http_max_module);//开辟所有http模块的srv_confctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);//开辟所有http模块的loc_confctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
}
其中ngx_http_max_module表示有多少个http模块, 对于每个main块,各个http模块对会有一个上下文结构,存放各模块关心的main结构配置。server块,loction块也是如此,各个http模块对会有一个上下文结构,存放各模块关心的server, loction结构配置。
接下来对于main块,创建各个模块关心的上下文结构;对于server块,创建各个模块关心的上下文结构; 对于loction块,创建各个模块关心的上下文结构
//开始解析http块
static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{for (m = 0; ngx_modules[m]; m++) {//只处理http模块,其它模块忽略if (ngx_modules[m]->type != NGX_HTTP_MODULE) {continue;}module = ngx_modules[m]->ctx; //该http模块在http类模块中的位置mi = ngx_modules[m]->ctx_index; //创建main块结构if (module->create_main_conf) {ctx->main_conf[mi] = module->create_main_conf(cf);}//创建server块结构if (module->create_srv_conf) {ctx->srv_conf[mi] = module->create_srv_conf(cf);}//创建loc块结构if (module->create_loc_conf) {ctx->loc_conf[mi] = module->create_loc_conf(cf);}}
}
同样,create_main_conf, create_srv_conf, create_loc_conf只是创建了模块的上下文结构,然后给成员赋初值。此时还没有解析具体的http模块的配置,稍后会进行解析具体的http配置信息。创建http模块上下文结构后的内存布局如下:
接下来将开始解析http模块,函数ngx_http_block会开始调用ngx_conf_parse递归解析。这个时候从解析main块进入解析http块。递归解析完http块后,会返回继续解析main块。
static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{//开始解析http块cf->module_type = NGX_HTTP_MODULE; //只解析http块cf->cmd_type = NGX_HTTP_MAIN_CONF;//递归解析http块rv = ngx_conf_parse(cf, NULL);
}
而在解析http块时,如果遇到"server {"则会从http块递归进入到server块。递归解析完server块后,会返回继续解析http块。ngx_http_core_server函数负责解析server块。
static char * ngx_http_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
{pcf = *cf;cf->ctx = ctx;cf->cmd_type = NGX_HTTP_SRV_CONF; //只解析server块//开始解析server块rv = ngx_conf_parse(cf, NULL);
}
而在解析server块时,如果遇到"location {"则会从server块递归进入到location块。递归解析完location块后,会返回继续解析server块。函数ngx_http_core_location中负责解析loaction块
//解析location配置
static char * ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
{//开始解析local结构save = *cf;cf->ctx = ctx;cf->cmd_type = NGX_HTTP_LOC_CONF;rv = ngx_conf_parse(cf, NULL);
}
文字描述起来比较吃力,比较难理解,还是上图吧!
五、配置解析源码分析
以上是分析nginx对配置文件的解析流程,现在来分析下解析代码的实现。而解析配置文件的过程还是比较复杂的,细节的东西还是留给读者吧,这里只是梳理下解析配置的框架代码,后续也会将详细注释的源码上传到github中。
解析配置文件的入口为ngx_conf_parse,该函数会间接被递归调用,每次进入函数会触发三种状态中的一种。例如,首次调用函数时,将触发读取配置操作,这个时候状态为parse_file,开始打开文件,准备缓冲区。第二次调用时,则不需要指定filename,参数可以为空,表示已经打开过文件,开始处理复杂块。这个时候状态为parse_block,这种情况是在递归调用过程才会发生。例如解析http块时,会递归调用函数解析server块。parse_param参数只用于解析命令行参数,例如执行./nginx -g "daemon on"时,才会有这种状态。
//解析nginx配置文件,将解析后的配置值保存到各个模块的上下文结构中
char * ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename)
{enum {parse_file = 0, //开始解析配置文件parse_block, //解析复杂块parse_param //解析命令行参数,例如 nginx -g "daemon on"} type;//解析文件状态if (filename) {//打开文件fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);//系统调用获取文件状态,例如获取文件大小ngx_fd_info(fd, &cf->conf_file->file.info);cf->conf_file->buffer = &buf;//分配4K缓冲区,存放从配置文件读取的数据buf.start = ngx_alloc(NGX_CONF_BUFFER, cf->log);buf.pos = buf.start;buf.last = buf.start;buf.end = buf.last + NGX_CONF_BUFFER;buf.temporary = 1; //临时缓冲区,内容可以修改//文件状态结构,每次从位置中读取数据后,会记录已经读取到文件中的哪个位置cf->conf_file->file.fd = fd;cf->conf_file->file.name.len = filename->len;cf->conf_file->file.name.data = filename->data;cf->conf_file->file.offset = 0;cf->conf_file->file.log = cf->log;cf->conf_file->line = 1;//解析文件状态type = parse_file; } else if (cf->conf_file->file.fd != NGX_INVALID_FILE) {//解析复杂块状态type = parse_block;} else {//解析命令行参数状态type = parse_param;}for ( ;; ){//解析配置文件中的每一行记录,会把解析后的结果记录到ngx_conf_s中的args数组中保存。例如解析到worker_process 2;则args下标0存放worker_process,下标1存放2这个值rc = ngx_conf_read_token(cf);//解析复杂块完成,例如:解析完了event块,http块等。可以递归返回到上一层if (rc == NGX_CONF_BLOCK_DONE) {goto done;}//整个文件解析完成if (rc == NGX_CONF_FILE_DONE){goto done;}//开始解析复杂块,例如在处理http命令时会间接递归调用ngx_conf_parse函数本身if (rc == NGX_CONF_BLOCK_START){}//这个回调函数一般用于处理text/html text/css这种样式//回调函数为ngx_http_core_types。一般不走这个逻辑,而是走ngx_conf_handler逻辑if (cf->handler){rv = (*cf->handler)(cf, NULL, cf->handler_conf);continue;}//根据解析的配置项,查找相应的命令rc = ngx_conf_handler(cf, rc);}
}
ngx_conf_read_token主要做了两件事;1、判断是否需要从配置文件读取数据到缓冲区中,一般情况下在缓冲区数据都解析完成后,会从配置文件读取新数据到缓冲区。这部分内容可以参考"nginx配置解析之缓冲区管理"这篇文件的分析,这里就不在重复了。 2、解析配置文件中的每一行,从而得到配置项 配置值,并把结果保存到数组中。其中配置项与配置值之间使用空格隔开,在解析每一行时就会解析找到配置项与配置值之间的空格,从而获取到配置项。 每行以";"结尾,进而把找到的上一个空格与“;”之间的内容当做配置值。
下面这个代码段就是以空格提取配置项,或者以";"提取配置值。进而将配置项保存到数组下标为0的位置,配置值保存到数组下标为1的位置。
static ngx_int_t ngx_conf_read_token(ngx_conf_t *cf)
{//查找到配置项与配置值的使用空格分割,或者一行结束时,说明找到配置项,或者配置值else if (ch == ' ' || ch == '\t' || ch == CR || ch == LF|| ch == ';' || ch == '{'){last_space = 1;found = 1;}//查到到一行的配置信息if (found){ //获取一个空间,存放配置信息word = ngx_array_push(cf->args);word->data = ngx_pnalloc(cf->pool, b->pos - start + 1);//拷贝配置信息for (dst = word->data, src = start, len = 0;src < b->pos - 1;len++){ *dst++ = *src++;}}
}
而下面的代码段表示在已经找到配置项的条件下,跳过配置项之后的所有空格,开始查找配置值。
static ngx_int_t ngx_conf_read_token(ngx_conf_t *cf)
{//在已经找到空格情况下,跳过所有空格,从空格之后的内容查找//例如:worker_process 2; 查找到worker_process后第一个空格后,跳过所有的空格//last_space初始值为1if (last_space) {if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) {continue;}//使用局部变量start,记录每一个有效内容开始。最终start与pos之间的内容构成配置项,或者配置值start = b->pos - 1;start_line = cf->conf_file->line;switch (ch){case ';':case '{': //开始处理复杂块return NGX_OK;case '}': //复杂块解析结束return NGX_CONF_BLOCK_DONE;case '#': //标记为注释sharp_comment = 1;continue;}}
}
太细节的东西真不好分析,也没有必要。细节的东西还是读者自己慢慢分析吧! 到此nginx.conf配置文件的解析已经全部分析完成了。下一篇文章将分析http块的合并流程。
nginx配置解析流程相关推荐
- nginx配置解析之配置合并
上一篇文章分析了nginx.conf配置解析流程,解析完成后会把各个配置项存放到各个模块的上下文结构中.但此时还没有对http模块.server模块.location模块公共部分进行合并处理.所谓的合 ...
- nginx配置解析之缓冲区管理
nginx服务器的master进程在解析nginx.conf时,会使用一个4k大小的缓存区存放部分配置文件信息.nginx会从配置文件中读取4k大小的内容到缓冲区,之后对缓冲区中的内容进行逐个字符扫描 ...
- Linux之Nginx配置解析PHP
location ~ \.php${include fastcgi_params;fastcgi_pass unix:/tmp/php-fcgi.sock;fastcgi_index index.ph ...
- nginx html解析插件,nginx配置信息的解析流程
nginx配置信息的解析流程 2011年9月9日 1,744 次浏览 请关注最新修正合订: 这一系列的文章还是在09年写的,存在电脑里很久了,现在贴出来.顺序也不记得了,看到那个就发那个吧,最近都会发 ...
- Nginx 源码分析-- 模块module 解析执行 nginx.conf 配置文件流程分析 一
搭建nginx服务器时,主要的配置文件 nginx.conf 是部署和维护服务器人员经常要使用到的文件, 里面进行了许多服务器参数的设置.那么nginx 以模块 module为骨架的设计下是如何运用模 ...
- Nginx 配置实现web解析php代码 过程记录
[Nginx配置] Nginx本身不支持对php的解析,需要将php代码转发到php-fpm 进程管理器来交给php解析器解析代码. 重要的配置注意注释位置: user www www; # 用户 用 ...
- 三、nginx服务的nginx.conf的参数配置解析
前一篇:二.nginx服务的nginx.conf配置参数解析 后一篇:四.nginx服务器的参数配置解析 目录 一.虚拟主机设定模块 1.upstream模块配置样式 1.1.默认配置 1.2.wei ...
- Java-Mybatis(二): Mybatis配置解析、resultMap结果集映射、日志、分页、注解开发、Mybatis执行流程分析
Java-Mybatis-02 学习视频:B站 狂神说Java – https://www.bilibili.com/video/BV1NE411Q7Nx 学习资料:mybatis 参考文档 – ht ...
- nginx配置防止域名恶意解析
前几发生一件事情,就是通过nginx日志发现有一个域名恶意指向到了我的服务器,大家可以去查查域名恶意解析可能会造成的危害.由于我是用的nginx配置了一个反向代理,所以直接配置nginx就可以实现域名 ...
最新文章
- python 实现函数的递归
- Fiddler抓包使用教程-乱码处理 Decode
- 深入学习SAP UI5框架代码系列之三:HTML原生事件 VS UI5 Semantic事件
- 关闭eslint检验;vue-cli3搭建的vue项目关闭eslint;脚手架3关闭eslint;
- 电脑键盘练习_三款神器!超越键盘飞毛腿!
- Shell循环输入符合条件为止
- Android自定义UI实例
- python求解线性规划问题
- Spring 4.x vs Spring 5.x
- 3D hand pose:BMC
- STC89C52驱动W25Q32测试笔记
- 绘图与滤镜全面解析--Quartz 2D 、Core Image
- shell的一些基础
- 【Vue】下载依赖包node_modules 打包项目
- matalb曲线图只有点没有线_身高170公分,体重170磅的女孩,身材健硕,没有一点多余赘肉...
- 生化危机绝密报告2java,生化危机-绝密任务V2.1
- DeepLAC论文笔记
- 上海科技大学计算机本科2020,上海科技大学2020年本科招生简章
- 2023 新年倒计时HTML源码
- 我的Java后端书架 (2016年暮春3.0版)