其实实现Nginx的每一个模块都是在实现ngx_module_t结构体,当你实现了这个结构你的模块也就实现了!
Nginx强大的功能,在一定程度上是依赖于对配置文件的配置,因此要正确的解析用户的配置文件就显得格外重要。从Nginx中解析出的所有配置信息都是由ngx_cycle_t结构体中的conf_ctx存储的,它是这样定义的
struct ngx_cycle_s{ ....... void ****conf_ctx; ...... }
她是一个指针数组,其中每一个元素又指向一个指针数组。存储结构类似于下图:
这样的指针指来指去,显得有点乱,但是Nginx正是利用了指针的强大功能,把所有的配置信息有条不紊的组织了起来。至于内部如何组织的我也没太高明白,这边博文住一套是开发一个模块来演示,自己定义的配置项如何在Ngins中解析出来,共模块使用。
1,、定义自己配置项的结构体。
//存储配置项参数的结构体 //由于Nginx框架提供了预设的14个回调方法,用于解析配置文件,所以在这里定义15个成员用于实现14种解析方法的实现和自定义解析方法 typedef struct{ ngx_str_t r_str; ngx_int_t r_num; ngx_flag_t r_flag; size_t r_size; ngx_array_t* r_str_array; ngx_array_t* r_keyval; off_t r_off; ngx_msec_t r_msec; time_t r_sec; ngx_bufs_t r_bufs; ngx_uint_t r_enum_seq; ngx_uint_t r_bitmask; ngx_uint_t r_access; ngx_path_t* r_path; ngx_str_t r_myprase; }ngx_http_mytest4_loc_conf_t;
2,定义自己的creat_loc_conf方法,用于产生自己配置结构体的指针,供Nginx框架使用
//Nginx定义了三个级别的配置main、srv、loc,分别表示直接出现在http{}、server{}、location{}块内的配置项,当配置文件中出现http{}时,Nginx会接管http块的解析,http框架会调用所有可能实现的create_main_conf,create_srv_conf,create_loc_conf方法生成存储main级别的配置项的结构体,当遇到server{}块时,http框架会调用所有可能实现的create_srv_conf,create_loc_conf方法生成存储srv级别的配置项的结构体,遇到location{}块时,http框架会调用所有可能实现的create_loc_conf方法生成存储loc级别的配置项的结构体, //普通的http模块一般只实现craete_loc_conf方法,因为他只关心匹配特定的URL请求。 //实现自己的create_loc_conf方法,并初始化部分成员(初始化是Nginx框架内置解析函数所要求的) static void * ngx_http_mytest4_craete_loc_conf(ngx_conf_t *cf){ //此方法就是用来生成自定义结构体的地址 ngx_http_mytest4_loc_conf_t *conf; printf("create_loc_conf!!\n"); conf = ngx_pcalloc(cf->pool,sizeof(ngx_http_mytest4_loc_conf_t)); if(NULL == conf){ return NGX_CONF_ERROR; } //如果使用内置的解析函数,则必须初始化 conf->r_flag = NGX_CONF_UNSET; conf->r_num = NGX_CONF_UNSET; conf->r_str_array = NGX_CONF_UNSET_PTR; conf->r_keyval = NULL; conf->r_off = NGX_CONF_UNSET; conf->r_msec = NGX_CONF_UNSET_MSEC; conf->r_sec = NGX_CONF_UNSET; conf->r_size = NGX_CONF_UNSET_SIZE; return conf; }
3、定义配置项解析相关的结构体
//定义模块配置文件参数 ,指定配置项的解析函数 static ngx_command_t ngx_http_mytest4_commands[] = { { //解析ngx_str_t类型的配置项,配置项的名称是test_str,其后只能有一个参数,将它保存在ngx_http_mytest_loc_conf_t结构的r_str成员中 ngx_string("test_str"), //配置项类型,即定义他可以出现的位置 NGX_HTTP_MAIN_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, //处理配置项参数的函数,使用内置的ngx_conf_set_str_solt, ngx_conf_set_str_slot, //指示配置项所处内存的相对位置,因为http模块可能会定义多个结构体,此成员说明用那个级别的结构体存储 NGX_HTTP_LOC_CONF_OFFSET, //指示当前配置文件在配置项的结构体中的的偏移位置,这种工作不需要用户来完成,可以使用offsetof宏来实现 //define offsetof(type,member) (size_t)&(((type*)0)->member) offsetof(ngx_http_mytest4_loc_conf_t,r_str), //配置项读取后的处理方法 NULL }, { //配置项的参数只有一个,且只能是数字,配置项的名称是test_num,将他的参数保存在ngx_http_mytest_loc_conf_t结构的r_num成员中 ngx_string("test_num"), NGX_HTTP_MAIN_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, //NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS, ngx_conf_set_num_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_mytest4_loc_conf_t,r_num), NULL }, { //配置项的名称是test_flag,其后的参数必须是on或者off,将他的参数保存在ngx_http_mytest_loc_conf_t结构的r_flag成员中,此成员必须在create_loc_conf方法中初始化 ngx_string("test_flag"), NGX_HTTP_MAIN_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_FLAG, //NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_mytest4_loc_conf_t,r_flag), NULL }, { //配置项的名称是test_size,想表达的含义是空间大小,参数后可以有单位,如m,M,k,K(1k=1024),不允许出现g和G,解析后的单位是字节,解析后报存在gx_http_mytest_loc_conf_t结构的r_size成员中,此成员必须在create_loc_conf方法中初始化 ngx_string("test_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, //NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_mytest4_loc_conf_t,r_size), NULL }, { //配置项名称是test_str_array,希望出现多个同名配置项,配个配置项后跟一个字符串参数,使用ngx_conf_set_str_array_solt方法可以把所有参数都以ngx_str_t类型存放到ngx_array_t的队列中,解析后保存在gx_http_mytest_loc_conf_t结构的r_str_array成员中 ngx_string("test_str_array"), NGX_HTTP_MAIN_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, //NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS, ngx_conf_set_str_array_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_mytest4_loc_conf_t,r_str_array), NULL }, { //与上面的ngx_conf_set_str_array_solt解析方法类似,只是他要求后跟两个参数,表示key/value,如果ngx_array_t*类型的r_keyval存储,则必须设置NGX_CONF_TAKE2,表示后跟两个参数 ngx_string("test_keyval"), NGX_HTTP_MAIN_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE2, //NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE2, ngx_conf_set_keyval_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_mytest4_loc_conf_t,r_keyval), NULL }, { //与ngx_conf_set_size_solt方法类似,只不过ngx_conf_set_off_solt方法允许单位使用g和G,解析后的偏移量是字节单位的, ngx_string("test_off"), NGX_HTTP_MAIN_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, //NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS, ngx_conf_set_off_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_mytest4_loc_conf_t,r_off), NULL }, { //配置项名为test_msec,要表达的是时间的长短,可以使用s(秒,如果不使用单位则默认为秒),m(分钟),h(小时),d(天),w(周),M(月),y(年),解析后的单位是毫秒, ngx_string("test_msec"), NGX_HTTP_MAIN_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, //NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_mytest4_loc_conf_t,r_msec), NULL }, { //与上面类似,只不过解析后的单位是秒 ngx_string("test_sec"), NGX_HTTP_MAIN_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, //NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS, ngx_conf_set_sec_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_mytest4_loc_conf_t,r_sec), NULL }, { //ngx_conf_set_bufs_solt方法要求配置项后必须跟两个参数,通常第一个参数表示缓冲区的个数,第二个参数表示单个缓冲区的空间大小(使用的单位和ngx_conf_set_size_solt的单位一致),解析后大小以字节为单位 ngx_string("test_bufs"), NGX_HTTP_MAIN_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE2, //NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS, ngx_conf_set_bufs_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_mytest4_loc_conf_t,r_bufs), NULL }, { //test_enum_seq用来表示枚举配置项,也就是他的参数只能从给定的范围中取,解析后的参数为uint类型,注意:为了实现枚举,需要自己定义枚举类型,并通过psot指针传入,即第六个参数 ngx_string("test_enum"), NGX_HTTP_MAIN_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, //NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS, ngx_conf_set_enum_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_mytest4_loc_conf_t,r_enum_seq), test4_enums //传入枚举类型 }, { //与上面类似,配置项也必须是枚举类型,差别在效率方面,ngx_conf_set_bitmask_solt可以按照位比较,效率更高, ngx_string("test_bitmask"), NGX_HTTP_MAIN_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, //NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS, ngx_conf_set_bitmask_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_mytest4_loc_conf_t,r_bitmask), test4_bitmasks//传入枚举类型 }, { //ngx_conf_set_access_solt用来解析读取权限,配置项后可以跟1到3个参数,解析后是一个uint类型(权限的掩码) ngx_string("test_access"), NGX_HTTP_MAIN_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE123, //NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS, ngx_conf_set_access_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_mytest4_loc_conf_t,r_access), NULL }, { //ngx_conf_set_path_solt用来解析路径,后可以跟1-4个参数,第一个参数必须是路径,其余参数必须是整数(大部分情况下可以不使用),会将参数解析后保存在ngx_path_t类型的结构中, ngx_string("test_path"), NGX_HTTP_MAIN_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1234, //NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS, ngx_conf_set_path_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_mytest4_loc_conf_t,r_path), NULL }, { //自定义解析函数 ngx_string("test_myparse"), //配置项类型,即定义他可以出现的位置 NGX_HTTP_MAIN_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, //NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS, //处理配置项参数的函数,函数在下面定义 ngx_conf_set_myparse, //在配置文件中的偏移量 NGX_HTTP_LOC_CONF_OFFSET, //预设的解析方法配置项 0, //配置项读取后的处理方法 NULL }, //command数组要以ngx_null_command结束 //#define ngx_null_command {ngx_null_string,0,NULL,0,0,NULL} ngx_null_command };
上面指定了15个配置项参数的解析函数,其中前14个都是使用框架提供的解析方法,因此部分配置项,需要传递自定义的结构体参数供框架使用,自定义的两个结构体如下:
//用于测试ngx_conf_set_enum_slot方法,他的值必须是枚举中的一个 static ngx_conf_enum_t test4_enums[]={ {ngx_string("apple"),1}, {ngx_string("banana"),2}, {ngx_string("orange"),3}, {ngx_null_string,0} }; //用于测试ngx_conf_set_bitmask_slot方法 static ngx_conf_bitmask_t test4_bitmasks[]={ {ngx_string("good"),0x0002}, {ngx_string("better"),0x0004}, {ngx_string("best"),0x0008}, {ngx_null_string,0} };
其中第15个配置项使用了自己的解析函数,同时在这个方法中指定了匹配这个localtion时执行的回调方法,以展示我们解析出的配置项:
//自定义的配置项解析函数,当配置项中出现test_myparse配置项时将调用这个函数 static char * ngx_conf_set_myparse(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { printf("myparse!!\n"); //conf参数是http框架传递给用户在ngx_http_mytest_craete_loc_conf函数分配的结构体 ngx_http_mytest4_loc_conf_t *mycf = conf; //cf中的成员args是一个ngx_array_t类型的队列,他的成员都是ngx_str_t类型的字符串,用value指向ngx_array_t的elts成员,value[1]就是第一个参数,value[2]就是第二个参数,args中的nelts表示参数的个数 ngx_str_t *value = cf->args->elts; if(cf->args->nelts>1){//第一个参数 //直接赋值,ngx_str_t只是指针的传递 mycf->r_myprase = value[1]; } //----------------------------------------------- //ckcf并不是指特定的location块内的数据结构,他可以是mian、srv、loc级别的配置项 //每个http{},sever{},location{}都有一个ngx_http_core_loc_conf_t类型的数据结构 ngx_http_core_loc_conf_t *clcf; //找到mytest配置项所在的配置块 clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); //http框架在处理用户请求进行到NGX_HTTP_CONTENT_PHASE阶段是,如果请求的主机名,URI与配置项所在的配置块相匹配时,就调用 //clcf中的handle方法处理这个请求 //NGX_HTTP_CONTENT_PHASE用于处理http请求内容的阶段,这是大部分http模块通常介入的阶段 clcf->handler = ngx_http_mytest4_handler; //------------------------------------------------------------------ return NGX_CONF_OK; }
4、定义模块上下文,我们只实现了creat_loc_conf方法:
//mytest模块上下文,都为NULL即是说在http框架初始化时没有什么要做 static ngx_http_module_t ngx_http_mytest4_module_ctx = { NULL, NULL, NULL, NULL, NULL, NULL, ngx_http_mytest4_craete_loc_conf, //自定义的配置项结构体生成函数 NULL };
5,定义自己的模块,在编译时加入到全局的ngx_modules数组中,这样在Nginx初始化时会调用模块的所有初始化方法
ngx_module_t ngx_http_mytest4_module = { NGX_MODULE_V1, //由Nginx定义的宏来初始化前七个成员 &ngx_http_mytest4_module_ctx, //模块的上下文结构体,指向特定模块的公共方法 ngx_http_mytest4_commands, //处理配置项的结构体数组 NGX_HTTP_MODULE, //模块类型 //Nginx在启动停止过程中七个执行点的函数指针 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NGX_MODULE_V1_PADDING //由Nginx定义的宏定义剩下的8个保留字段 };
6,实现自己展示的回调函数:
//实际完成处理的回调函数 static ngx_int_t ngx_http_mytest4_handler(ngx_http_request_t *r) { printf("handle!!\n"); //请求方法 if (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD))) { return NGX_HTTP_NOT_ALLOWED; } //不处理请求的包体,直接丢弃。但这一步也是不可省略的,他是接受包体的一种方法,只不过是简单的丢弃, //如果不接受,客户端可能会再次试图发送包体,而服务器不接受就会造成客户端发送超时 ngx_int_t rc = ngx_http_discard_request_body(r); if (rc != NGX_OK) { return rc; } //存储配置项参数的结构体 ngx_http_mytest4_loc_conf_t *mycf; //取得模块上下文 mycf = ngx_http_get_module_loc_conf(r,ngx_http_mytest4_module); //构造响应头部 ngx_str_t type = ngx_string("text/plain"); //以下是将内存中的字符串做为包体发送 ngx_str_t response = ngx_string("conf:\ntest_str\t:\t%s;\ntest_num\t:\t%d;\ntest_size\t:\t%dbyte;\ntest_myparse\t:\t%s;\n"); ngx_str_t test_str = mycf->r_str; ngx_int_t test_num = mycf->r_num; size_t test_size = mycf->r_size; ngx_str_t data = mycf->r_myprase; //8是共有四个占位符,每个占位符两个字节4*2=8 printf("sizeof(test_num):%d\tsizeof(test_size):%d\n",sizeof(test_num),sizeof(test_size)); int response_len = response.len+test_str.len+sizeof(test_num)+sizeof(test_size)+data.len-8+1; r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = response_len; r->headers_out.content_type = type; //发送http头部,其中也包括响应行 rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { return rc; } ngx_buf_t *b; //根据请求中传来的内存池对象,创建内存buf b = ngx_create_temp_buf(r->pool, response_len); if (b == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_snprintf(b->pos,response_len,(char *)response.data,test_str.data,test_num,test_size,data.data); //有效内容到last结束 b->last = b->pos + response_len; //因为ngx_buf_t可以由ngx_chain_t链表链起来,last_buf可以标记这是最后一块待处理的缓冲区,简化处理 b->last_buf = 1; //将内存buf用链表链起来,作为ngx_http_output_filter的跌入个参数 ngx_chain_t out; out.buf = b; //标记这是最后一个ngx_chain_t out.next = NULL; return ngx_http_output_filter(r, &out); }
7、测试:
配置文件如下:(实现了个别的解析和输出,其余的是一样的!)
展示:
8、总结:
使用Nginx框架提供的配置项解析函数,可以大大提高开发效率,同时减少代码下出错的可能,但是注意,使用有些解析方法是,结构体中的相应项,一定要初始化,以防解析错误。