上一篇文章分析了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配置解析流程相关推荐

  1. nginx配置解析之配置合并

    上一篇文章分析了nginx.conf配置解析流程,解析完成后会把各个配置项存放到各个模块的上下文结构中.但此时还没有对http模块.server模块.location模块公共部分进行合并处理.所谓的合 ...

  2. nginx配置解析之缓冲区管理

    nginx服务器的master进程在解析nginx.conf时,会使用一个4k大小的缓存区存放部分配置文件信息.nginx会从配置文件中读取4k大小的内容到缓冲区,之后对缓冲区中的内容进行逐个字符扫描 ...

  3. Linux之Nginx配置解析PHP

    location ~ \.php${include fastcgi_params;fastcgi_pass unix:/tmp/php-fcgi.sock;fastcgi_index index.ph ...

  4. nginx html解析插件,nginx配置信息的解析流程

    nginx配置信息的解析流程 2011年9月9日 1,744 次浏览 请关注最新修正合订: 这一系列的文章还是在09年写的,存在电脑里很久了,现在贴出来.顺序也不记得了,看到那个就发那个吧,最近都会发 ...

  5. Nginx 源码分析-- 模块module 解析执行 nginx.conf 配置文件流程分析 一

    搭建nginx服务器时,主要的配置文件 nginx.conf 是部署和维护服务器人员经常要使用到的文件, 里面进行了许多服务器参数的设置.那么nginx 以模块 module为骨架的设计下是如何运用模 ...

  6. Nginx 配置实现web解析php代码 过程记录

    [Nginx配置] Nginx本身不支持对php的解析,需要将php代码转发到php-fpm 进程管理器来交给php解析器解析代码. 重要的配置注意注释位置: user www www; # 用户 用 ...

  7. 三、nginx服务的nginx.conf的参数配置解析

    前一篇:二.nginx服务的nginx.conf配置参数解析 后一篇:四.nginx服务器的参数配置解析 目录 一.虚拟主机设定模块 1.upstream模块配置样式 1.1.默认配置 1.2.wei ...

  8. Java-Mybatis(二): Mybatis配置解析、resultMap结果集映射、日志、分页、注解开发、Mybatis执行流程分析

    Java-Mybatis-02 学习视频:B站 狂神说Java – https://www.bilibili.com/video/BV1NE411Q7Nx 学习资料:mybatis 参考文档 – ht ...

  9. nginx配置防止域名恶意解析

    前几发生一件事情,就是通过nginx日志发现有一个域名恶意指向到了我的服务器,大家可以去查查域名恶意解析可能会造成的危害.由于我是用的nginx配置了一个反向代理,所以直接配置nginx就可以实现域名 ...

最新文章

  1. python 实现函数的递归
  2. Fiddler抓包使用教程-乱码处理 Decode
  3. 深入学习SAP UI5框架代码系列之三:HTML原生事件 VS UI5 Semantic事件
  4. 关闭eslint检验;vue-cli3搭建的vue项目关闭eslint;脚手架3关闭eslint;
  5. 电脑键盘练习_三款神器!超越键盘飞毛腿!
  6. Shell循环输入符合条件为止
  7. Android自定义UI实例
  8. python求解线性规划问题
  9. Spring 4.x vs Spring 5.x
  10. 3D hand pose:BMC
  11. STC89C52驱动W25Q32测试笔记
  12. 绘图与滤镜全面解析--Quartz 2D 、Core Image
  13. shell的一些基础
  14. 【Vue】下载依赖包node_modules 打包项目
  15. matalb曲线图只有点没有线_身高170公分,体重170磅的女孩,身材健硕,没有一点多余赘肉...
  16. 生化危机绝密报告2java,生化危机-绝密任务V2.1
  17. DeepLAC论文笔记
  18. 上海科技大学计算机本科2020,上海科技大学2020年本科招生简章
  19. 2023 新年倒计时HTML源码
  20. 我的Java后端书架 (2016年暮春3.0版)

热门文章

  1. 如何将一段声音变成二维码呢?
  2. 2.React Native Flex布局介绍以及实践
  3. 【英语词组】恋恋不忘Day4-3
  4. 2021-2027年中国手机面板视窗玻璃行业市场研究及前瞻分析报告
  5. 手机余额查询(限制重庆)
  6. 转: 写给想成为前端工程师的同学们 (from 360前端团队)
  7. 费用报销java_ERP费用报销操作与设计--开源软件诞生31
  8. Shuffle Read Time调优
  9. 翻译文件名称,如何操作将中文名称翻译成英文名称
  10. airpod蓝牙耳机音量大解决办法_airpods怎么调节音量