Nginx:解析HTTP配置的流程
参考资料:深入理解Nginx(陶辉)
书中有详细的讲解,这里只用本人的理解梳理一下该流程。
一点提议:对于像我这样的新手,面对暂时看不懂章节,建议先往下看一下(可能就会有新的理解或灵感),而不要死磕在某一章节。
几个重要的数据结构
定义一个用于测试的结构体
我们的测试模块将使用该结构体来存放配置信息,该结构只存放一个ngx_str_t。
typedef struct { ngx_str_t my_str; } ngx_http_mytest_conf_t;
先看看ngx_http_module_t的定义
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); //合并server级别的配置项 void *(*create_loc_conf)(ngx_conf_t *cf); //当需要创建数据结构用户存储loc级别的全局配置项时候调用 char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf); //合并location级别的配置项 } ngx_http_module_t;
HTTP框架定义了3个级别的配置main、srv、loc,分别表示直接出现在http{}、server{}、location{}块内的配置项。
例如:当解析遇到http{}配置块时,会调用create_main_conf回调函数来创建并返回每个HTTP模块对应的结构体(对于我们的测试模块是ngx_http_mytest_conf_t)。
我们的mytest模块实现create_loc_conf的是ngx_http_mytest_create_loc_conf方法
static void *ngx_http_mytest_create_loc_conf(ngx_conf_t *cf) { //创建mytest模块对应的结构体ngx_http_mytest_conf; ngx_http_mytest_conf_t *mycf; mycf=(ngx_http_mytest_conf_t *)ngx_pcalloc(cf->pool,sizeof(ngx_http_mytest_conf_t)); if(mycf==NULL){ return NULL; } return mycf; }
那么Nginx是怎么保存这些配置信息的呢?
再看看ngx_http_conf_ctx_t结构
typedef struct { /* 指针数组,数组中的每个元素指向所有HTTP模块create_main_conf方法产生的结构体*/ void **main_conf; /* 指针数组,数组中的每个元素指向所有HTTP模块create_srv_conf方法产生的结构体*/ oid **srv_conf; /* 指针数组,数组中的每个元素指向所有HTTP模块create_loc_conf方法产生的结构体*/ void **loc_conf; } ngx_http_conf_ctx_t;
用于定义模块的配置参数的结构体ngx_command_t
typedef struct ngx_command_s ngx_command_t; struct ngx_command_s { ngx_str_t name; ngx_uint_t type; char *(*set)(ngx_conf_t *cf,ngx_command_t *cmd,void *conf); ngx_uint_t conf; ngx_uint_t offset; void *post; }
其中各个成员代表的意义如下:
name:配置项的名称
type:配置项的参数类型、参数个数以及配置项可以在哪些位置出现(http、server、location等)
set:处理配置项的回调方法。Nginx提供了14个预设的解析配置项的方法。
conf:对于HTTP模块,conf是必须设置的,它的取值范围见下表
offset:当前配置项在整个存储配置项的结构体中的偏移位置。对于使用Nginx预设的解析方法:
Nginx首先通过conf成员找到应该用哪个结构体来存放,然后通过offset成员找到这个结构体中的相应成员,以便存放该配置。
在mytest模块中,我们将这样定义一个ngx_command_t数组
static ngx_command_t ngx_http_mytest_commands[] = { { ngx_string("test_str"), //配置项名称 NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, //配置项只能出现在location块中并且配置参数数量为1个 ngx_conf_set_str_slot, //使用预设的解析方法解析配置参数,表示我们希望用ngx_str_t类型的变量来保存这个配置项的参数 NGX_HTTP_LOC_CONF_OFFSET, //使用create_loc_conf方法产生的结构体来存储解析出的配置项参数 offsetof(ngx_http_mytest_conf_t,my_str); //与上面两个成员一起使用,通过这3个成员来找到该配置参数存放的位置(ngx_http_mytest_conf_t.my_str)。 NULL }, ngx_null_command };
HTTP配置的流程
1.主循环调用配置文件解析器解析nginx.conf文件。
2.当发现配置文件中含有http{}关键字时,HTTP框架开始启动。
3.初始化所有HTTP模块的序列号,并创建ngx_http_conf_ctx_t结构。
4.调用每个HTTP模块的create_main_conf、create_srv_conf、create_loc_conf方法。
5.把各HTTP模块上述3个方法返回的地址依次保存到ngx_http_conf_ctx_t结构体的3个数组中(如果某个模块没有定义相应的方法,则为NULL)。
6.调用每个HTTP模块的preconfiguration方法。
7.如果preconfiguration返回失败,那么Nginx进程将会停止。
8.HTTP框架开始循环解析nginx.conf文件中http{...}里面的所有配置项
9.配置文件解析器在检测到一个配置项后,会遍历所有HTTP模块,检查它们的ngx_command_t数组中的name项是否与配置项名相同。
10.如果找到一个HTTP模块对这个配置项感兴趣,就调用ngx_command_t结构中的set方法来处理该配置项。
11.如果set方法返回失败,那么Nginx进程会停止。
12.配置文件解析器继续检查配置项。如果发现server{...}配置项,就会调用ngx_http_core_module模块来处理。
13.ngx_http_core_module模块在解析server{...}之前,也会如第三步一样建立ngx_http_conf_ctx_t结构,并调用每个HTTP模块的create_srv_conf、create_loc_conf回调方法。
14.将上一步各HTTP模块返回的指针地址保存到ngx_http_conf_ctx_t对应的数组中。
15.开始调用配置文件解析器来处理server{...}里面的配置项。
16.继续重复第9步的过程,遍历nginx.conf中当前server{...}内的所有配置项。
17.配置文件解析器继续解析配置项,如果发现当前server块已经遍历到尾部,则返回ngx_http_core_module模块。
18.返回配置文件解析器继续解析后面的配置项。
19.配置文件解析器继续解析配置项,如果发现处理到了http{...}的尾部,返回个HTTP框架继续处理。
20.调用merge_srv_conf、merge_loc_conf等方法合并这些不同块中每个HTTP模块分配的数据结构。
21.HTTP框架处理完毕http配置项,返回给配置文件解析器继续处理其他http{...}外的配置项。
22.配置文件解析器处理完所有配置项后告诉Nginx主循环配置项解析完毕,这是Nginx才会启动Web服务器。
注意:上面还有一些我们没有列出来的步骤(如发现其他server块或者location块)。它们都会创建ngx_http_conf_ctx_t结构。
HTTP配置模型的内存布局
mytest模块的完整代码
1 #include <ngx_config.h> 2 #include <ngx_core.h> 3 #include <ngx_http.h> 4 5 typedef struct{ 6 ngx_str_t my_str; 7 }ngx_http_mytest_conf_t; 8 9 static ngx_int_t 10 ngx_http_mytest_handler(ngx_http_request_t *r); 11 12 //创建mytest模块对应的结构体ngx_http_mytest_conf; 13 static void *ngx_http_mytest_create_loc_conf(ngx_conf_t *cf) 14 { 15 ngx_http_mytest_conf_t *mycf; 16 mycf=(ngx_http_mytest_conf_t *)ngx_pcalloc(cf->pool,sizeof(ngx_http_mytest_conf_t)); 17 if(mycf==NULL){ 18 return NULL; 19 } 20 return mycf; 21 } 22 23 static ngx_command_t ngx_http_mytest_commands[] = { 24 { 25 ngx_string("response_line"), //配置项名称 26 NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, //配置项只能出现在location块中并且配置参数数量为1个 27 ngx_conf_set_str_slot, //使用预设的解析方法解析配置参数,表示我们希望用ngx_str_t类型的变量来保存这个配置项的参数 28 NGX_HTTP_LOC_CONF_OFFSET, //使用create_loc_conf方法产生的结构体来存储解析出的配置项参数 29 offsetof(ngx_http_mytest_conf_t,my_str), //与上面两个成员一起使用,通过这3个成员来找到该配置参数存放的位置(ngx_http_mytest_conf_t.my_str)。 30 NULL 31 }, 32 ngx_null_command 33 }; 34 35 //挂载handler 36 static ngx_int_t 37 ngx_http_mytest_init(ngx_conf_t *cf) 38 { 39 ngx_http_handler_pt *h; 40 ngx_http_core_main_conf_t *cmcf; 41 42 cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); 43 44 h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers); 45 if (h == NULL) { 46 return NGX_ERROR; 47 } 48 49 *h = ngx_http_mytest_handler; 50 51 return NGX_OK; 52 } 53 54 static ngx_http_module_t ngx_http_mytest_module_ctx={ 55 NULL, 56 ngx_http_mytest_init, 57 NULL, 58 NULL, 59 NULL, 60 NULL, 61 ngx_http_mytest_create_loc_conf, 62 NULL 63 }; 64 65 ngx_module_t ngx_http_mytest_module={ 66 NGX_MODULE_V1, 67 //指向ngx_http_module_t结构体 68 &ngx_http_mytest_module_ctx, 69 //用来处理nginx.conf中的配置项 70 ngx_http_mytest_commands, 71 //表示该模块的类型 72 NGX_HTTP_MODULE, 73 NULL, 74 NULL, 75 NULL, 76 NULL, 77 NULL, 78 NULL, 79 NULL, 80 NGX_MODULE_V1_PADDING 81 }; 82 83 static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r) 84 { 85 //判断请求方法是否为GET或者HEAD,否则返回405 Not Allowed 86 if(!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))){ 87 return NGX_HTTP_NOT_ALLOWED; 88 } 89 90 //丢弃请求中的包体 91 ngx_int_t rc=ngx_http_discard_request_body(r); 92 if(rc!=NGX_OK){ 93 return rc; 94 } 95 96 //设置相应头 97 ngx_str_t type=ngx_string("text/plain"); 98 //获取配置参数 99 ngx_http_mytest_conf_t* my_conf; 100 my_conf = ngx_http_get_module_loc_conf(r, ngx_http_mytest_module); 101 ngx_str_t response=my_conf->my_str; 102 r->headers_out.status=NGX_HTTP_OK; 103 r->headers_out.content_length_n=response.len; 104 r->headers_out.content_type=type; 105 106 //发送HTTP头部 107 rc=ngx_http_send_header(r); 108 if(rc==NGX_ERROR||rc>NGX_OK||r->header_only){ 109 return rc; 110 } 111 112 //构造ngx_buf_t结构体准备发送包体 113 ngx_buf_t *b; 114 b=ngx_create_temp_buf(r->pool,response.len); 115 if(b==NULL){ 116 return NGX_HTTP_INTERNAL_SERVER_ERROR; 117 } 118 ngx_memcpy(b->pos,response.data,response.len); 119 b->last=b->pos+response.len; 120 b->last_buf=1; 121 122 //构造发送时的ngx_chain_t结构体 123 ngx_chain_t out; 124 out.buf=b; 125 out.next=NULL; 126 return ngx_http_output_filter(r,&out); 127 }
按照http://www.cnblogs.com/runnyu/p/4871866.html编译安装该模块
在nginx.conf文件中默认server块下配置如下信息
location /response {
response_line HelloWorld\r\n;
}
使用telnet查看结果