nginx http模块开发实例(2) - 过滤模块

2018-07-25 22:48:28

本例子实现了在响应体的前面或者后面附加数据。

过滤模块有一点需要额外注意,就是过滤的回调函数有可能会被调用多次,所以一定要有请求期间的全局变量控制,防止错误发生。

模块指令  

Syntax:	myfilter on|off;
Default:
Context: http, server, location

一旦开启了该指令,那么我们就可以通过url参数名 head 和 tail 来附加数据。

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>


static void *ngx_http_myfilter_create_conf(ngx_conf_t *cf);
static ngx_int_t ngx_http_my_headers_filter(ngx_http_request_t *r);
static ngx_int_t
ngx_http_my_body_filter(ngx_http_request_t *r, ngx_chain_t *in);
static ngx_int_t ngx_http_myfilter_init(ngx_conf_t *cf);
static char *
ngx_http_myfilter_merge_conf(ngx_conf_t *cf, void *parent, void *child);

static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;

//同一个 location 里的全局配置
//一般配置后就不会改变它
typedef struct {
	ngx_flag_t enable;
} ngx_http_myfilter_conf_t;

//同一个请求里保存的数据,一般是动态可变
//比如可以根据参数来改变
typedef struct {
	ngx_str_t head;
	ngx_str_t tail;
} ngx_http_myfilter_ctx_t;

static ngx_command_t  ngx_http_myfilter_commands[] = {

    { ngx_string("myfilter"),
    	NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG|NGX_CONF_TAKE1,
		ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
	  //如果要调用系统默认的解析函数,就得设置偏移,比如上面的 ngx_conf_set_flag_slot
	  offsetof(ngx_http_myfilter_conf_t, enable),
      NULL },

      ngx_null_command
};


static ngx_http_module_t  ngx_http_myfilter_module_ctx = {
    NULL,                          /* preconfiguration */
	ngx_http_myfilter_init,        /* postconfiguration */

    NULL,                          /* create main configuration */
    NULL,                          /* init main configuration */

    NULL,                          /* create server configuration */
    NULL,                          /* merge server configuration */

	ngx_http_myfilter_create_conf,    /* create location configuration */
    ngx_http_myfilter_merge_conf      /* merge location configuration */
};


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
};

//每个location都创建自己独立的 ngx_http_myfilter_conf_t配置。
static void *
ngx_http_myfilter_create_conf(ngx_conf_t *cf)
{
	ngx_http_myfilter_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_myfilter_conf_t));
    if (conf == NULL) {
        return NULL;
    }

    //因为要调用 ngx_conf_set_flag_slot 函数解析,所以必须初始化
    //如果自己解析,则可以不设置
    conf->enable = NGX_CONF_UNSET;

    return conf;
}

//因为配置在 main/srv/loc都可以配置,所以需要合并
static char *
ngx_http_myfilter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
{
	ngx_http_myfilter_conf_t *prev = parent;
	ngx_http_myfilter_conf_t *conf = child;

    ngx_conf_merge_value(conf->enable, prev->enable, 0);

    return NGX_CONF_OK;
}

//把响应头过滤函数和响应体过滤函数链到两个全局函数指针去
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_my_headers_filter;

	ngx_http_next_body_filter = ngx_http_top_body_filter;
	ngx_http_top_body_filter = ngx_http_my_body_filter;

	return NGX_OK;
}

//过滤响应头
static ngx_int_t
ngx_http_my_headers_filter(ngx_http_request_t *r){
	ngx_http_myfilter_conf_t *conf;
	ngx_http_myfilter_ctx_t *ctx;

	//如果状态码非200,交由后面处理
	if(r->headers_out.status != NGX_HTTP_OK){
		return ngx_http_next_header_filter(r);
	}

	//获取请求中自己的上下文
	ctx = ngx_http_get_module_ctx(r, ngx_http_myfilter_module);
	if(ctx){
		//如果已经存在,证明已经被调用过一次,直接交由下一个过滤模块处理。
		return ngx_http_next_header_filter(r);
	}

	//获取当前 location 保存的配置
	conf = ngx_http_get_module_loc_conf(r, ngx_http_myfilter_module);
	if(conf->enable == 0){
		//没有开启
		return ngx_http_next_header_filter(r);
	}

	ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_myfilter_ctx_t));
	if(ctx == NULL){
		return NGX_ERROR;
	}

	ctx->head.len = 0;
	ctx->tail.len = 0;

	//设置到上下文里去
	ngx_http_set_ctx(r, ctx, ngx_http_myfilter_module);

	//只处理mime type以 text/开头的
	if(ngx_strncasecmp(r->headers_out.content_type.data, (u_char *)"text/", 5) == 0){
		//通过变量获取uri参数
		ngx_http_variable_value_t  *vv;
		ngx_str_t v_head = ngx_string("arg_head");
		ngx_str_t v_tail = ngx_string("arg_tail");
		ngx_uint_t key1 = ngx_hash_key(v_head.data, v_head.len);
		ngx_uint_t key2 = ngx_hash_key(v_tail.data, v_tail.len);

		//获取并设置变量值
		vv = ngx_http_get_variable(r, &v_head, key1);
		if(vv != NULL && vv->not_found != 1){
			ctx->head.data = vv->data;
			ctx->head.len = vv->len;

			//修改 content-length
			if(r->headers_out.content_length_n > 0){
				r->headers_out.content_length_n += ctx->head.len;
			}
		}

		//获取并设置变量值
		vv = ngx_http_get_variable(r, &v_tail, key2);
		if(vv != NULL && vv->not_found != 1){
			ctx->tail.data = vv->data;
			ctx->tail.len = vv->len;

			//修改 content-length
			if(r->headers_out.content_length_n > 0){
				r->headers_out.content_length_n += ctx->tail.len;
			}
		}


	}

	//printf("%d\n", (int)r->headers_out.content_length_n);

	return ngx_http_next_header_filter(r);
}

//过滤响应体
static ngx_int_t
ngx_http_my_body_filter(ngx_http_request_t *r, ngx_chain_t *in){
	ngx_int_t rc;
	ngx_uint_t last;
	ngx_http_myfilter_ctx_t *ctx;
	ngx_chain_t *cl;
	ngx_buf_t *b;

	ctx = ngx_http_get_module_ctx(r, ngx_http_myfilter_module);
	if(ctx == NULL){
		return ngx_http_next_body_filter(r, in);
	}


	if(ctx->head.len > 0){
		b = ngx_create_temp_buf(r->pool, ctx->head.len);
		if (b == NULL) {
			return NGX_ERROR;
		}
		ngx_memcpy(b->pos, ctx->head.data, ctx->head.len);
		b->last = b->pos + ctx->head.len;

		//防止多次回调重复添加前缀
		ctx->head.len = 0;

		//添加到链的前面
		cl = ngx_alloc_chain_link(r->pool);
		cl->buf = b;
		cl->next = in;

		in = cl;
	}

	//printf("%d %d\n", in->buf->last_buf, in->buf->last_in_chain);

	//如果有需要附加的数据
	if(ctx->tail.len > 0){
		//当到了最后一个buf时,我们把它改成不是最后一个buf
		//因为我们还要附加自己的数据
		last = 0;
		for (cl = in; cl; cl = cl->next) {
	        if (cl->buf->last_buf) {
	            cl->buf->last_buf = 0;
	            cl->buf->last_in_chain = 1;
	            cl->buf->sync = 1;
	            last = 1;
	        }
	    }

		rc = ngx_http_next_body_filter(r, in);

		//当到了最后一个buf,那么就在结尾附上我们希望加的数据
		if (rc == NGX_ERROR || !last) {
	        return rc;
	    }

		//当发送完前面的数据了,那么该发送我们的结尾数据
		b = ngx_create_temp_buf(r->pool, ctx->tail.len);
		if (b == NULL) {
			return NGX_ERROR;
		}
		ngx_memcpy(b->pos, ctx->tail.data, ctx->tail.len);
		b->last = b->pos + ctx->tail.len;
		b->last_buf = 1;
		b->flush = 1;    //非常重要,要求 nginx 立即输出

		ngx_chain_t   out;
		out.buf = b;
		out.next = NULL;

		ctx->tail.len = 0;  //防止多次调用

		return ngx_http_output_filter(r, &out);
	}

	return ngx_http_next_body_filter(r, in);

}
[root@192 nginx]# ll /tmp/nginx_m/filter/
-rw-r--r--. 1 root root  183 Jul 25 11:00 config
-rw-r--r--. 1 root root 8013 Jul 25 22:38 ngx_http_myfilter_module.c

[root@192 nginx]# cat /tmp/nginx_m/filter/config 
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"

上面的目录和文件自己新建,然后进入 nginx 源码目录进行编译

./configure --prefix=/usr/local/nginx --add-module=/tmp/nginx_m/filter/

make

make install

配置例子

location / {
    root html;
    myfilter on;
}
[root@192 html]# curl 'http://192.168.1.10/index.html'
http://www.freecls.com
[root@192 html]# curl 'http://192.168.1.10/index.html?head=hello,'
hello,http://www.freecls.com
[root@192 html]# curl 'http://192.168.1.10/index.html?head=hello,&tail=,done'
hello,http://www.freecls.com,done


备注

1.测试环境centos7 64位,nginx版本为 1.14.0。
2..原文地址http://www.freecls.com/a/2712/d9


©著作权归作者所有
收藏
推荐阅读
简介
天降大任于斯人也,必先苦其心志。