在简单的http模块中,mytest
配置项后面没有跟任何参数。本篇博客学习讨论HTTP模块是怎样获得感兴趣的配置项。
处理http配置项可以分为下面4个步骤:
(1)创建数据结构用于存储配置项对应的参数。
(2)设定配置项在nginx.conf中出现时的限制条件与回调方法。
(3)实现第二步中的回调方法,或者使用Nginx框架预设的14个回调方法。
(4)合并不同级别的配置块中出现的同名配置项。
这四个步骤是通过ngx_http_module_t
以及ngx_command_t
有机的结合起来。
本例中将在nginx.conf
中添加一些配置项来自己编写模块进行解析。
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
location = / {
root html;
index index.html index.htm;
}
location = /zxtest{
test_str "zhangxiaoha";
test_num 23;
test_flag off;
test_size 10k;
mytest;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
我们希望通过自己模块将配置项中的参数保存到数据结构中,并通过访问localhost/zxtest
将内容显示到浏览器上。
1.数据结构 |
用一个结构体来包含所有我们感兴趣的参数,上述配置文件中,有4个带配置项值的配置项,因此我们的结构体与之对应:
typedef struct{
ngx_str_t my_str;
ngx_int_t my_num;
ngx_flag_t my_flag;
size_t my_size;
}ngx_http_mytest_conf_t;
在陶辉老师的《深入理解Nginx》一书中为了说明14种预设配置项的解析方法,因此在结构体中包含了14个成员,我为了简单起见,就用了4个。
2.预设的配置项解析方法 |
配置项解析方法通过ngx_command_t中的char *(*set)(ngx_conf_t *cf,ngx_command_t*cmd,void *conf)
回调成员设置,关于ngx_command_t结构的详细声明见core/ngx_conf_file.h
。
Nginx有14种预设的配置项解析方式:
配置项的参数是on或者off,当为on时结构体中对应变量被设置为1,否则被设置为0。
本例中,我们希望在nginx.conf中有一个配置项的名称为test_flag
,并且它后面携带的参数是on或者off,
那么我们可以使用生成的ngx_http_mytest_conf_t结构体中的以下成员保存:
ngx_flag_t my_flag;
当test_flag
配置项的值为on时,结构体中my_flag的值将设置为1
用来设置ngx_str_t
类型的变量。
该方法会将同名配置项的参数以ngx_str_t
的类型存放到ngx_array_t队列容器中
例如nginx.conf
有如下配置:
location ...{
test_str_array Content-Length;
test_str_array Content-Encoding;
}
则my_str_array->nelts
的值是2,表示出现了两个test_str_array
配置项。
并且,my_str_array->elts
指向ngx_str_t
类型组成的数组。
因此,我们进行如下访问:
ngx_str_t*pstr = mycf->my_str_array->elts;
//pstr[0].len=14
//pstr[0].data="Content-Length"
//pstr[1].len=16
//pstr[1].data="Content-Encoding"
与ngx_conf_set_str_array_slot
类似,只不过其数组元素是一个Key/Value
类型,
因此,test_keyval
配置项的后面需要跟两个参数,并且在command
中的type
参数指定为 NGX_CONF_TAKE2
这种key-value的结构体如下定义:
// ~/nginx/src/core/ngx_string.h
typedef struct{
ngx_str_t key;
ngx_str_t value;
} ngx_keyval_t;
处理带一个参数的配置项。
如果使用test_num
表示这个配置项名称,在nginx.conf中有如下:
location ...{
test_num 10
}
则在结构体中ngx_int_t my_num
成员将会变成10
配置项希望表达的含义是空间的大小,那么该回调函数就十分适合。
用结构体ngx_http_mytest_conf_t
中的size_t my_size
来存储参数。
如果配置了test_size 10k;
,则该参数将被设置成10240
配置项表达的含义是空间的偏移位置,则使用该内置回调函数。
结果最终将保存在ngx_http_mytest_conf_t
的off_t my_off
成员中
解析之后将以毫秒的形式存储在ngx_http_mytest_conf_t
中的 ngx_msec_t my_msec;
成员中。
例如test_msec 1d
,将设置86400000,表示一天的毫秒数。
跟上述类似,单位是秒。
配置项需要携带两个参数,第一个参数表示缓存区的个数,
第二个参数表示缓存区的大小。
用来保存这两个参数的是一个结构体变量:
// nginx/src/core/ngx_buf.h
typedef struct{
ngx_int_t num;
size_t size;
}ngx_bufs_t;
//区别 ngx_buf_t
nginx的enum类型定义如下:
//nginx/src/core/ngx_conf_file.h
typedef struct {
ngx_str_t name;
ngx_uint_t value;
}ngx_conf_enum_t;
事先定义一个ngx_conf_enum_t
类型的数组,作用类似于字典,例如:
static ngx_conf_enum_t test_enums[]={
{ngx_string("apple"),1},
...
...
{ngx_null_string,0}
}
在nginx.conf
中配置项test_enum
的参数必须是上述数组中出现的,
否则,Nginx将会报错。而保存enum类型的是一个ngx_uint_t
类型的变量。
跟ngx_conf_enum_t
类似,需要事先定义一个ngx_conf_bitmask_t
类型的数组。
// nginx/core/src/ngx_conf_file.h
typedef struct{
ngx_str_t name;
ngx_uint_t mask;
}ngx_conf_bitmask_t;
用来设置读写权限。
用来设置路径
3.创建与合并数据结构 |
在ngx_http_module_t
结构体中定义了8个回调函数(详见http/ngx_http_config.h
),其中:
typedef struct {
//...
//当需要创建数据结构用于存储main级别的全局配置项时,可以通过create_main_conf回调方法创建存储全局配置项的结构体
//(直属于http{}块的配置项)
void *(*create_main_conf)(ngx_conf_t *cf);
//初始化main级别配置项
char *(*init_main_conf)(ngx_conf_t *cf, void *conf);
//创建存储srv级别配置项的结构体(server块)
void *(*create_srv_conf)(ngx_conf_t *cf);
//合并main级别和srv级别下的同名配置项
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
//创建用于存储loc级别(直属于location{})的配置项
void *(*create_loc_conf)(ngx_conf_t *cf);
//合并srv级别和location级别下的同名配置项
char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
}ngx_http_module_t;
有三组创建与合并的函数,目的是处理不同配置块中(http{} server{} location{}
)的配置项。
以loc级别的一组创建与合并的函数为例,create_loc_conf
回调函数用来主要为我们定义的结构体分配空间ngx_pcalloc
。 merge_loc_conf
回调函数用来合并server{}
配置块(相对于loc来说是parent)中的相同配置项的参数,nginx也提供相关的函数来指导怎样合并(例如以同一配置项是上一级别的参数为准,还是本级别的参数为准)。
4.自定义配置项处理函数 |
现在我们要自定义配置项处理方法,将我们从配置项中读出的参数输出到浏览器中(text/html)。
实际上,在Nginx开发简单的HTTP模块就使用了自定义的配置项处理方法,在《深入理解Nginx》中,作者自定义postconfiguration
回调在终端打印配置项参数信息。
这里我在自定义配置项处理的回调函数中ngx_conf_http_mytest
设置真正的处理工作的ngx_http_mytest_handler
,发送http响应。
5.完整代码 |
ngx_addon_name=ngx_http_mytest_module
HTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_module.c"
//ngx_http_mytest_module.c
#include
#include
#include
static ngx_int_t
ngx_http_mytest_handler(ngx_http_request_t *r);
static char *ngx_conf_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void*
ngx_http_mytest_create_loc_conf(ngx_conf_t *cf);
static char*
ngx_http_mytest_merge_loc_conf(ngx_conf_t *cf,void *parent,void *child);
//保存参数的数据结构
typedef struct{
ngx_str_t my_str;
ngx_int_t my_num;
ngx_flag_t my_flag;
size_t my_size;
}ngx_http_mytest_conf_t;
//合并loc级别的配置项参数
static char*
ngx_http_mytest_merge_loc_conf(ngx_conf_t *cf,void *parent,void *child){
ngx_http_mytest_conf_t *prev = parent;
ngx_http_mytest_conf_t *conf = child;
ngx_conf_merge_str_value(conf->my_str,prev->my_str,"defaultstr");
return NGX_CONF_OK;
}
//创建loc级别的配置项参数
static void*
ngx_http_mytest_create_loc_conf(ngx_conf_t *cf){
ngx_http_mytest_conf_t *conf;
conf = ngx_pcalloc(cf->pool,sizeof(ngx_http_mytest_conf_t));
if(NULL == conf){
return NGX_CONF_ERROR;
}
conf->my_str.len = 0;
conf->my_str.data = NULL;
conf->my_flag = NGX_CONF_UNSET;
conf->my_num = NGX_CONF_UNSET;
conf->my_size = NGX_CONF_UNSET_SIZE;
return conf;
}
//http模块山下文
static ngx_http_module_t
ngx_http_mytest_module_ctx={
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
ngx_http_mytest_create_loc_conf,
ngx_http_mytest_merge_loc_conf
};
//定义模块的配置文件参数
static ngx_command_t
ngx_http_mytest_commands[]={
{
ngx_string("test_flag"),
NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_mytest_conf_t,my_flag),
NULL
},
{
ngx_string("test_str"),
//携带一个参数且可以出现在http/server/location配置块
NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
//nginx预设的方法
ngx_conf_set_str_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_mytest_conf_t, my_str),
NULL
},
{
ngx_string("test_num"),
NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_mytest_conf_t, my_num),
NULL
},
{
ngx_string("test_size"),
NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
ngx_conf_set_size_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_mytest_conf_t, my_size),
NULL
},
{
ngx_string("mytest"),
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_conf_http_mytest,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
ngx_null_command
};
//nginx模块定义
ngx_module_t ngx_http_mytest_module={
NGX_MODULE_V1,
&ngx_http_mytest_module_ctx,
ngx_http_mytest_commands,
NGX_HTTP_MODULE,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NGX_MODULE_V1_PADDING
};
//自定义配置项处理函数
static char*
ngx_conf_http_mytest(ngx_conf_t *cf,
ngx_command_t *cmd,
void*conf)
{
ngx_http_core_loc_conf_t *clcf;
clcf=ngx_http_conf_get_module_loc_conf(cf,ngx_http_core_module);
//handler是实际处理
clcf->handler=ngx_http_mytest_handler;
return NGX_CONF_OK;
}
//handler回调
static ngx_int_t
ngx_http_mytest_handler(ngx_http_request_t *r)
{
//存储配置项参数
ngx_http_mytest_conf_t *elcf;
elcf=ngx_http_get_module_loc_conf(r,ngx_http_mytest_module);
if (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD |NGX_HTTP_POST))) {
return NGX_HTTP_NOT_ALLOWED;
}
//丢弃报文
ngx_int_t rc=ngx_http_discard_request_body(r);
if(NGX_OK!=rc){
return rc;
}
//这里使用"text/html"如果使用"text/plain"浏览器会将下载文本当做bin文件下载下来
ngx_str_t type=ngx_string("text/html");
ngx_str_t format=ngx_string("helloworld,test_str=%v,test_flag=%i,test_num=%i,test_size=%z");
ngx_str_t test_str_arg=elcf->my_str;
ngx_int_t test_num_arg=elcf->my_num;
ngx_flag_t test_flag_arg=elcf->my_flag;
size_t test_size_arg=elcf->my_size;
int data_len=format.len+test_str_arg.len+1;
r->headers_out.status=NGX_HTTP_OK;
r->headers_out.content_length_n = data_len;//响应包包体内容长度
r->headers_out.content_type = type;
rc=ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
return rc;
}
ngx_buf_t *b;
b = ngx_create_temp_buf(r->pool,data_len);
if (b == NULL){
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ngx_snprintf(b->pos,data_len,(char *)format.data,&test_str_arg,test_flag_arg,test_num_arg,test_size_arg);
b->last = b->pos + data_len;
b->last_buf = 1;
ngx_chain_t out;
out.buf = b;
out.next = NULL;
return ngx_http_output_filter(r, &out); //发送响应报文
}
6.测试 |
将模块编入nginx,重启nginx,在浏览器输入特定url,效果如下:
<
7.参考 |
1.《深入理解Nginx》(第二版)
2.http://blog.csdn.net/xiajun07061225/article/details/9147265