今天终于来到了nginx过滤器模块,这个过滤器模块也是nginx使用中的最后一部分了,之后可以会简单讲解一下nginx源码,毕竟nginx这么优秀的设计,还是值得学习一下源码。
5.1 开发一个简单的过滤器模块
我们这次就开发一个在给客户端返回的响应包体前加一段字符串“[wo add haha]”,就这么随意,当然也支持在nginx的配置文件中打开或关闭。
5.1.1 编写config文件
按照之前的习惯,还是新创建一个文件夹,存放我们自己写的nginx模块,在文件夹中添加config文件,相信看到前面的朋友们,都会知道config文件是干什么的,其实就是给nginx源码配置的时候指定如何编译的,然后我们这个过滤器模块的config文件编写跟HTTP模块的差不多,下面来看一看:
ngx_addon_name=ngx_http_myfilter_module
HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES ngx_http_myfilter_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_myfilter_module.c"
我观察了一波,发现就是$HTTP_FILTER_MODULES这个不一样,当然文件名也不一样,这个是我们自己指定的,可以忽略,剩下的就是指定和这个模块是什么类型了,因为我们这个是过滤器模块,所以要填HTTP_FILTER_MODULES。
5.1.2 配置项
上一节刚讲了配置项,所以这次我们就可以直接支持了,上次讲HTTP模块的时候,是为了简化。
我们希望在nginx.conf中有一个控制当前HTTP过滤器模块是否生效的配置项,它的参数值为on或者oiff,分别表示开启或者关闭。按照之前的用法,需要建立一个自己本地的配置数据结构体,其中使用ngx_flag_t类型的enable变量来存储这个参数值,
typedef struct {
ngx_flag_t enable;
} ngx_http_myfilter_conf_t;
//创建一个本地内存
static void *ngx_http_myfilter_creat_conf(ngx_conf_t *cf)
{
ngx_http_myfilter_conf_t *mycf;
mycf = (ngx_http_myfilter_conf_t *) ngx_pcalloc (cf->pool, sizeof(ngx_http_myfilter_conf_t));
if*(mycf == NULL) {
return NULL;
}
mycf->enable = NGX_CONF_UNSET;
return mycf;
}
//合并配置文件
static char* ngx_http_myfilter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_myfilter_conf_t *prev = (ngx_http_myfilter_conf_t *)parent;
ngx_http_myfilter_conf_t *conf = (ngx_http_myfilter_conf_t *)child;
ngx_conf_merge_value(conf->enable, prev->enable, 0);
return NGX_CONF_OK;
}
我就不分开写了,反正之前也讲过配置文件的写法了,一个函数负责创建配置文件的结构体,一个函数负责合并,因为在nginx中,可以在不同配置块中定义相同的过滤器模块,到底是启用还是关闭,还是需要我们代码决定。
5.1.3 HTTP过滤模块
//匹配的命令
static ngx_command_t ngx_http_myfilter_commands[] = {
{
ngx_string("add_prefix"),
NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_myfilter_conf_t, enable), NULL
},
ngx_null_command
};
//http过滤器模块的定义
static ngx_http_module_t ngx_http_myfilter_module_ctx = {
NULL,
ngx_http_myfilter_init,
NULL,
NULL,
NULL,
NULL,
ngx_http_myfilter_creat_conf,
ngx_http_myfilter_merge_conf
};
//整个模块的配置
ngx_module_t ngx_http_myfilter_module = {
NGX_MODULE_V1,
&ngx_http_myfilter_module_ctx, /* module context */
ngx_http_myfilter_commands, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
命令数据结构和http模块的上下文和模块的数据结构,这3个结构是很重要的,也是我们像nginx注册这几个结构。
5.1.4 http模块处理函数
我们需要建立一个过滤器模块的上下文:
//HTTP头部处理方法在1个请求中只会被调用1次,但是包体处理方法中可以被调用几次,所以需要add_prefix来表示
typedef struct {
ngx_int_t add_prefix;
} ngx_http_myfilter_ctx_t;
为什么添加这个呢?是因为nginx是全异步操作,HTTP头部处理方法在1个请求中只会被调用一次,但包体处理方法可以在一次请求中可以被多次调用,我们定义这个函数,是怕多次调用,多次加前缀。
static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
static ngx_int_t ngx_http_myfilter_init(ngx_conf_t *cf )
{
//插入到头部处理方法链表的首部
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_myfilter_header_filter;
//插入到包体处理方法链表的首部
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_myfilter_body_filter;
return NGX_OK;
}
初始化HTTP模块,只要是定义过滤器模块的指针,就是构成一个链,等一下会分析,现在先明白next的指定是指向后一个过滤器模块,top指定是头指针。
//处理请求中的HTTP头部
static ngx_str_t filter_prefix = ngx_string("[wo add haha]");
static ngx_int_t ngx_http_myfilter_header_filter(ngx_http_request_t *r)
{
ngx_http_myfilter_conf_t *conf;
ngx_http_myfilter_ctx_t *ctx;
ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "header 1");
//判断是否返回成功
if( r
->headers_out.status != NGX_HTTP_OK ) {
return ngx_http_next_header_filter(r);
}
ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "header 2");
ctx = ngx_http_get_module_ctx(r, ngx_http_myfilter_module);
if(ctx) {
//该请求上下文已经存在,交由其他的处理
return ngx_http_next_header_filter(r);
}
ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "header 3");
//提取配置项的值
conf = ngx_http_get_module_loc_conf(r, ngx_http_myfilter_module);
ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "header 31 %p", conf);
if(conf->enable == 0) {
return ngx_http_next_header_filter(r);
}
ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "header 4");
//构造HTTP上下文结构体
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_myfilter_ctx_t));
if(ctx == NULL) {
return NGX_ERROR;
}
//为0表示不用加前缀
ctx->add_prefix = 0;
//将构造上下文设置到当前请求中
ngx_http_set_ctx(r, ctx, ngx_http_myfilter_module);
//判断只要text/plain请求才加入前缀
if((r->headers_out.content_type.len >= sizeof("text/plain") - 1
&& ngx_strncasecmp(r->headers_out.content_type.data, (u_char *) "text/plain",
sizeof("text/plain") -1) == 0) || (r->headers_out.content_type.len >= sizeof("text/html") - 1
&& ngx_strncasecmp(r->headers_out.content_type.data, (u_char *) "text/html",
sizeof("text/html") -1) == 0) )
{
ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "header ctx->add_prefix:%d", ctx->add_prefix);
//表示可以加入前缀
ctx->add_prefix = 1;
//由于我们要在包体前面加字符串,所以修改包体的长度
if(r->headers_out.content_length_n > 0) {
r->headers_out.content_length_n += filter_prefix.len;
}
}
return ngx_http_next_header_filter(r);
}
处理的请求HTTP头,这个函数的只要作用就是申请了过滤器模块的上下文结构体,并且绑定到r的指针中,等到处理包体的时候可以使用,剩下的处理把数据部分的长度增加。
static ngx_int_t ngx_http_myfilter_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
ngx_http_myfilter_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r, ngx_http_myfilter_module);
if(ctx == NULL && ctx->add_prefix != 1) {
//该请求上下文已经存在,交由其他的处理
return ngx_http_next_body_filter(r, in);
}
ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "boay ctx->add_prefix:%d", ctx->add_prefix);
//设置标志位为2,使再次被调用也不会再加前缀了
ctx->add_prefix = 2;
//分配内存
ngx_buf_t *b = ngx_create_temp_buf(r->pool, filter_prefix.len);
b->start = b->pos = filter_prefix.data;
b->last = b->pos + filter_prefix.len;
//添加到请求链表中
ngx_chain_t *cl = ngx_alloc_chain_link(r->pool);
cl->buf = b;
cl->next = in;
return ngx_http_next_body_filter(r, cl);
}
处理包体函数就是真正的添加了,nginx设计很优秀,只要往ngx_chain_t这个链表中插入即可,这个链表就是需要输出数据的链表,插入到头部之后,就可以输出了。
5.1.5 测试
编译:
./configure --add-module=/root/cloud_disk/04_nginx/mytest_module --add-module=/root/cloud_disk/04_nginx/myfilter_module
nginx编译可以添加多个模块,编译成功后,需要在配置文件中启用这个添加前缀的功能:
location / {
root html;
index index.html index.htm;
add_prefix on;
}
on是打开的意思,这样我们用网页测试一下:
成功了,在nginx的首页添加了 wo add haha,这串字符串。
5.1.6 总结
总体感觉这个nginx的过滤器模块是比较简单的,甚至比之前的http模块都简单,这个比较难的一点就是这个过滤器的链表怎么搭建起来的,其他的就按照流程在走,下面我们就来探讨一下过滤器的链表结构。
5.2 过滤器模块介绍
5.2.1 过滤器链表如何构成
既然一个请求会被所有的HTTP过滤器模块依次处理,那么我们就来分析分析这些HTTP过滤器模块是怎么组织成一起的。
我们是否想起上面写一个http模块的时候,定义了一次处理包头的函数指针,一个处理包体的函数指针。
static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
没错就是它两,这两个函数指针比较重要,接下来我们看看初始化函数中是怎么绑定的:
static ngx_int_t ngx_http_myfilter_init(ngx_conf_t *cf )
{
//插入到头部处理方法链表的首部
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_myfilter_header_filter;
//插入到包体处理方法链表的首部
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_myfilter_body_filter;
return NGX_OK;
}
这里我们是不是看到两个全部的函数,ngx_http_top_header_filter,ngx_http_top_body_filter。没错这两个就是nginx提供的过滤器的链表入口。
ngx_http_output_header_filter_pt ngx_http_top_header_filter;
ngx_http_output_body_filter_pt ngx_http_top_body_filter;
这么一看这两个家伙也是一个函数指针,不过是nginx定义的一个全局的函数指针。
还记得我们写的mytest模块的,发送头部的时候会调用一个ngx_http_send_header®;函数,我们可以看看这个函数:
ngx_int_t
ngx_http_send_header(ngx_http_request_t *r)
{
if (r->post_action) {
return NGX_OK;
}
if (r->header_sent) {
ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
"header already sent");
return NGX_ERROR;
}
if (r->err_status) {
r->headers_out.status = r->err_status;
r->headers_out.status_line.len = 0;
}
return ngx_http_top_header_filter(r);
}
在最后一行我们就看到了ngx_http_top_header_filter这个函数指针了,也就是说nginx发送包头的时候,会遍历这个链表,这个链表怎么建立起来的呢,我们等会在说,继续我们看看发送包体的函数ngx_http_output_filter(r, &out);
继续看看这个函数的实现:
ngx_int_t
ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
ngx_int_t rc;
ngx_connection_t *c;
c = r->connection;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http output filter \"%V?%V\"", &r->uri, &r->args);
rc = ngx_http_top_body_filter(r, in);
if (rc == NGX_ERROR) {
/* NGX_ERROR may be returned by any filter */
c->error = 1;
}
return rc;
}
果不其然这里也调用了ngx_http_top_body_filter这个函数指针,那接下我们就要看看这个链表是怎么建立起来的。
其实很简单的,我们来看看我们写的过滤器模块的初始化函数:
static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
static ngx_int_t ngx_http_myfilter_init(ngx_conf_t *cf )
{
//插入到头部处理方法链表的首部
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_myfilter_header_filter;
//插入到包体处理方法链表的首部
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_myfilter_body_filter;
return NGX_OK;
}
我们在模块中定义了两个指向下一个的指针,用static表示其他模块也可以用这个变量名,然后把ngx_http_top_header_filter指针赋值给next指针,然后再把ngx_http_myfilter_header_filter我们的这个模块使用的指针赋值给top指针,假如之前top指针就是gzip的过滤器模块,我们就把gzip的指定指向了我们的next指针,然后再把我们的add_prefix赋值给top指针,这样构建了一条链,如果这时候调用top指针就是调用了我们的add_prefix这个模块的函数,然后处理完了之后,我们会继续调用next指针处理下一个,我们看看myfilter的源码就能发现:
//处理请求中的HTTP头部
static ngx_str_t filter_prefix = ngx_string("[wo add haha]");
static ngx_int_t ngx_http_myfilter_header_filter(ngx_http_request_t *r)
{
ngx_http_myfilter_conf_t *conf;
ngx_http_myfilter_ctx_t *ctx;
...
return ngx_http_next_header_filter(r); //调用下一个过滤器模块
}
这个过滤器的链表就是这样构成的,nginx的设计确实很奇妙。
5.2.2 过滤器的链表顺序
HTTP过滤器模块之间的调用顺序还是很重要的,nginx在编译过程中就会决定HTTP过滤器模块的顺序,我们在哪里看呢?其实就是在ngx_modules这个数据中各模块的顺序决定的。
由于每隔HTTP过滤器模块的初始化的方法都会把自己加入到单链表的首部,所以,什么时候,以何种顺序调用这些HTTP过滤器模块的初始化方法,将会决定这些HTTP过滤器模块在单链表中的位置。
我们一般会把ngx_http_myfilter_init函数绑定成postconfiguration这个函数指针上,这种做法是比较可控的,也支持这个做法,过滤器模块的顺序是configure配置的时候指定的,也是官方写死了,如果我们需要改,可以在configure配置完成之后,直接修改ngx_modules这个数组,接下来我们看看这个数据:(过滤器部分)
&ngx_http_write_filter_module, //仅对包体做处理,向客户端发送HTTP响应
&ngx_http_header_filter_module, //仅对HTTP头部做处理
&ngx_http_chunked_filter_module, //支持chunk编码
&ngx_http_range_header_filter_module, //支持range协议
&ngx_http_gzip_filter_module, //仅对HTTP包体做处理,gzip相关
&ngx_http_postpone_filter_module, //仅对HTTP包体做处理
&ngx_http_ssi_filter_module, //支持ssl
&ngx_http_charset_filter_module, //可以将文本类型返回给用户的响应包
&ngx_http_userid_filter_module, //仅对HTTP头部做处理
&ngx_http_headers_filter_module, //仅对HTTP头部做处理
&ngx_http_myfilter_module, //第三方模块
&ngx_http_copy_filter_module, //仅对HTTP包体做处理
&ngx_http_range_body_filter_module, //处理请求中的Range信息
&ngx_http_not_modified_filter_module, //仅做头部处理
这个顺序是反过来的,因为每个模块添加到链表中都是头部插入,所以最后一个才是开始的,这里面的顺序是nginx固定了,还有处理我自己添加的ngx_http_myfilter_module这个模块后,其他模块都是官方默认的模块,简单的注释我也写了,不过我还没具体分析过,所以不是太清楚这些模块的具体含义,不过可以查查其他资料看看,了解了解。