Nginx专题(五、开发一个过滤器模块)

今天终于来到了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这个模块后,其他模块都是官方默认的模块,简单的注释我也写了,不过我还没具体分析过,所以不是太清楚这些模块的具体含义,不过可以查查其他资料看看,了解了解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值