其实实现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框架提供的配置项解析函数,可以大大提高开发效率,同时减少代码下出错的可能,但是注意,使用有些解析方法是,结构体中的相应项,一定要初始化,以防解析错误。