Nginx模块开发之http过滤器filter

什么是过滤模块

在这里插入图片描述
Nignx是一个代理服务器, 他前端被客户端请求,后端连接服务器。这里涉及的数据处理大概有

  1. 客户端请求数据,Nginx直接返回(handler 模块)
  2. 客户端请求数据,Nginx转发给服务器(upstream 模块)
  3. 服务器返回数据,Nginx转发给客户端(filter 模块)

Nginx相关数据结构介绍

ngx_module_t的数据结构

struct ngx_module_s {
    ngx_uint_t            ctx_index; //是哪个进程
    ngx_uint_t            index;	//进程id

    char                 *name;

    ngx_uint_t            spare0;
    ngx_uint_t            spare1;

    ngx_uint_t            version; //版本号
    const char           *signature; //签名证书

    void                 *ctx;//上下文
    ngx_command_t        *commands;//命令
    ngx_uint_t            type;// nginx模块类型

    ngx_int_t           (*init_master)(ngx_log_t *log);//

    ngx_int_t           (*init_module)(ngx_cycle_t *cycle); //模块启动时候

    ngx_int_t           (*init_process)(ngx_cycle_t *cycle); //进程启动时候
    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);
    void                (*exit_thread)(ngx_cycle_t *cycle);
    void                (*exit_process)(ngx_cycle_t *cycle);

    void                (*exit_master)(ngx_cycle_t *cycle);

    uintptr_t             spare_hook0;
    uintptr_t             spare_hook1;
    uintptr_t             spare_hook2;
    uintptr_t             spare_hook3;
    uintptr_t             spare_hook4;
    uintptr_t             spare_hook5;
    uintptr_t             spare_hook6;
    uintptr_t             spare_hook7;
};
typedef struct ngx_module_s ngx_module_t;

ngx_http_module_t数据结构

typedef struct {
    void        **main_conf;
    void        **srv_conf;
    void        **loc_conf;
} ngx_http_conf_ctx_t;


typedef struct {
    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);	// 解析配置文件之前
    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);	// 解析配置文件完成之后
// **_main_ **解析配置文件中http关键字的内部
    void       *(*create_main_conf)(ngx_conf_t *cf);	
    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);
// **_srv_ **解析配置文件中server关键字的内部
    void       *(*create_srv_conf)(ngx_conf_t *cf);	
    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
// **_loc_ **解析配置文件中location关键字的内部
    void       *(*create_loc_conf)(ngx_conf_t *cf);
    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;

ngx_command_s数据结构

struct ngx_command_s {
    ngx_str_t             name;
    ngx_uint_t            type;
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ngx_uint_t            conf;
    ngx_uint_t            offset;
    void                 *post;
};

相关宏定义

#define NGX_MODULE_V1                                                         
    NGX_MODULE_UNSET_INDEX, NGX_MODULE_UNSET_INDEX,                           
    NULL, 0, 0, nginx_version, NGX_MODULE_SIGNATURE

#define NGX_MODULE_V1_PADDING  0, 0, 0, 0, 0, 0, 0, 0


#define NGX_HTTP_MODULE           0x50545448   /* "HTTP" 模块*/

/* 以下宏定义为了去确定该项配置属于哪个类目下 
比如service 
比如location
*/
#define NGX_HTTP_MAIN_CONF        0x02000000
#define NGX_HTTP_SRV_CONF         0x04000000
#define NGX_HTTP_LOC_CONF         0x08000000
#define NGX_HTTP_UPS_CONF         0x10000000
#define NGX_HTTP_SIF_CONF         0x20000000
#define NGX_HTTP_LIF_CONF         0x40000000
#define NGX_HTTP_LMT_CONF         0x80000000

filter(过滤器)实现

Nginx模块开发流程

(1)定义一个模块名,ngx_module_t,选择好http模块NGX_HTTP_MODULE。
(2)定义cmd命令,有多少条cmd写多少条cmd,ngx_command_t。
(3)定义用来解析http block,ngx_http_module_t。
(4)执行过程实现添加模块。

Nginx 模块执行

(1)初始化。当进程启动的时候进行的模块初始化。
(2)解析conf文件。解析conf文件中模块的相关命令和设置。
(3)Nginx启动之后,有命令或请求到来时,处理请求的流程。

开发模块时,需要实现的主要是这三个流程的功能。

具体实现流程

create_loc_conf

内存池中分配一片kong空间,用以存储配置文件中指令对应的值

// void       *(*create_loc_conf)(ngx_conf_t *cf);
// 解析conf文件location关键字之前的动作
void  *ngx_http_fly_filter_create_loc_conf(ngx_conf_t *cf)
{
	ngx_http_filter_conf_t *conf = ngx_palloc(cf->pool, sizeof(ngx_http_filter_conf_t));
	if (conf == NULL)
		return NULL;

	conf->enable = NGX_CONF_UNSET;
	
	return conf;
}

merge_loc_conf

char  *ngx_http_fly_filter_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{

	ngx_http_filter_conf_t *prev = (ngx_http_filter_conf_t*)parent;
	//如果prefix中是on,那么next->enable的值就为1,这个是在ngx_conf_set_flag_slot
	//函数中设置的,即可以理解为将配置
	//文件中的on或者off转换为nginx内存中的1或者0
	ngx_http_filter_conf_t *next = (ngx_http_filter_conf_t*)child;

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

	return NGX_CONF_OK;
}

其中 ngx_conf_merge_value

#define ngx_conf_merge_value(conf, prev, default)                            
    if (conf == NGX_CONF_UNSET) {                                            
        conf = (prev == NGX_CONF_UNSET) ? default : prev;                    

postconfiguration

  1. 解析完毕conf文件后执行该指令,设置运行时候的回调函数
  2. 使用头插法,将header_filter 与 body_filter插入filter队列的头部
// ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);
// 解析完配置文件之后的动作,也就是解析完http关键字模块之后
ngx_int_t ngx_http_fly_filter_init(ngx_conf_t *cf)
{
	// 模块的初始化
	// http {  }

	// O->O->O->O
	// 多个模块的头插法,取出最前面的模块
	//top指向第一个,next指向第二个
	ngx_http_next_header_filter = ngx_http_top_header_filter;
	ngx_http_top_header_filter = ngx_http_fly_header_filter;

	ngx_http_next_body_filter = ngx_http_top_body_filter;
	ngx_http_top_body_filter = ngx_http_fly_body_filter;

	return NGX_OK;
}

修改header信息

这里仅仅修改要回发的内容长度,由于修改了body内容,那么header中的length字段自然要做出相应的修改

static ngx_str_t prefix = ngx_string("<h2>FLY. </h2>");
ngx_int_t ngx_http_fly_header_filter(ngx_http_request_t *r) {

	if (r->headers_out.status != NGX_HTTP_OK) {
		// 不正常返回,则进行next
		return ngx_http_next_header_filter(r);
	}

	//r->headers_out.content_type.len == sizeof("text/html")

	r->headers_out.content_length_n += prefix.len;

	return ngx_http_next_header_filter(r);
}

修改body信息

ngx_int_t ngx_http_fly_body_filter(ngx_http_request_t *r, ngx_chain_t *chain) {

	/*
	* 关于ngx_chain_t:
	* 在nginx中,有一个数据链,存放要发送的数据。
	* O->O->O->O
	* 每次send的是ngx_chain_t中的一个ngx_buf_t
	*/

	// 添加一个chain buffer
	ngx_buf_t *b = ngx_create_temp_buf(r->pool, prefix.len);
	b->start = b->pos = prefix.data;
	b->last = b->pos + prefix.len;

	ngx_chain_t *c1 = ngx_alloc_chain_link(r->pool);
	c1->buf = b;
	c1->next = chain;

	return ngx_http_next_body_filter(r, c1);

}

示例代码

这里主要实现了在返回的网页中添加一个内容。里面在重点地方附上了详细注释。
ngx_http_filter_module.c


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

typedef struct {
	ngx_flag_t enable;
}ngx_http_filter_conf_t;

static ngx_str_t prefix = ngx_string("<h2>FLY. </h2>");

static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;

// ngx_int_t (*ngx_http_output_header_filter_pt)(ngx_http_request_t *r)
// 添加头,header
ngx_int_t ngx_http_fly_header_filter(ngx_http_request_t *r) {

	if (r->headers_out.status != NGX_HTTP_OK) {
		// 不正常返回,则进行next
		return ngx_http_next_header_filter(r);
	}

	//r->headers_out.content_type.len == sizeof("text/html")

	r->headers_out.content_length_n += prefix.len;

	return ngx_http_next_header_filter(r);
}

// ngx_int_t (*ngx_http_output_body_filter_pt)(ngx_http_request_t *r, ngx_chain_t *chain)
// 添加内容,body
ngx_int_t ngx_http_fly_body_filter(ngx_http_request_t *r, ngx_chain_t *chain) {

	/*
	* 关于ngx_chain_t:
	* 在nginx中,有一个数据链,存放要发送的数据。
	* O->O->O->O
	* 每次send的是ngx_chain_t中的一个ngx_buf_t
	*/

	// 添加一个chain buffer
	ngx_buf_t *b = ngx_create_temp_buf(r->pool, prefix.len);
	b->start = b->pos = prefix.data;
	b->last = b->pos + prefix.len;

	ngx_chain_t *c1 = ngx_alloc_chain_link(r->pool);
	c1->buf = b;
	c1->next = chain;

	return ngx_http_next_body_filter(r, c1);

}

// ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);
// 解析完配置文件之后的动作,也就是解析完http关键字模块之后
ngx_int_t ngx_http_fly_filter_init(ngx_conf_t *cf)
{
	// 模块的初始化
	// http {  }

	// O->O->O->O
	// 多个模块的头插法,取出最前面的模块
	ngx_http_next_header_filter = ngx_http_top_header_filter;
	ngx_http_top_header_filter = ngx_http_fly_header_filter;

	ngx_http_next_body_filter = ngx_http_top_body_filter;
	ngx_http_top_body_filter = ngx_http_fly_body_filter;

	return NGX_OK;
}



// void       *(*create_loc_conf)(ngx_conf_t *cf);
// 解析conf文件location关键字之前的动作
void  *ngx_http_fly_filter_create_loc_conf(ngx_conf_t *cf)
{
	ngx_http_filter_conf_t *conf = ngx_palloc(cf->pool, sizeof(ngx_http_filter_conf_t));
	if (conf == NULL)
		return NULL;

	conf->enable = NGX_CONF_UNSET;
	
	return conf;
}


// char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
// 解析完配置文件location关键字之后的动作
// 模块可能在多个地方定义,这个函数合并所有的值一起使用
char  *ngx_http_fly_filter_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{

	ngx_http_filter_conf_t *prev = (ngx_http_filter_conf_t*)parent;
	ngx_http_filter_conf_t *next = (ngx_http_filter_conf_t*)child;
	
	// 合并enable的值
	ngx_conf_merge_value(next->enable, prev->enable, 0);

	return NGX_CONF_OK;
}

/*
struct ngx_command_s {
ngx_str_t             name;
ngx_uint_t            type;
char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
ngx_uint_t            conf;
ngx_uint_t            offset;
void                 *post;
};
*/

/*
// conf文件命令解析
char  *ngx_http_fly_filter_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
	char *p = conf;

	// 对应 ngx_http_fly_filter_create_loc_conf函数的conf->enable = NGX_CONF_UNSET;
	ngx_flag_t *flag = (p + cmd->offset);

	return NGX_CONF_OK;
}
*/


// conf文件中的每一行都是一个指令指令
ngx_command_t ngx_http_fly_filter_module_cmd[] = {
	{
		//命令名称,比如listen,定义了就可以在conf文件中使用,注意不能和其他的起冲突
		ngx_string("predix"),
		// 指示name命令放的位置在哪里以及可以带多少个参数,NGX_CONF_FLAGE表示开关标志
		// predix on/off
		NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_FLAG,
		// 命令解析,可以使用nginx内部的也可以自己实现
		ngx_conf_set_flag_slot,//ngx_http_fly_filter_set_slot,

		NGX_HTTP_LOC_CONF_OFFSET,

		// offsetof获取enable在结构体中的偏移位置
		offsetof(ngx_http_filter_conf_t,enable),
		NULL,
	},
	ngx_null_command
};


// 用来解析对应的conf文件,其实表示的就是模块定义中的上下文
static ngx_http_module_t ngx_http_fly_filter_module_ctx = {
	NULL,
	ngx_http_fly_filter_init,

	NULL,
	NULL,

	NULL,
	NULL,

	ngx_http_fly_filter_create_loc_conf,
	ngx_http_fly_filter_merge_loc_conf
};

// 模块定义
ngx_module_t ngx_http_fly_filter_module = {
	NGX_MODULE_V1,

	&ngx_http_fly_filter_module_ctx,
	ngx_http_fly_filter_module_cmd,

	// http的ascii值,指示是什么模块
	NGX_HTTP_MODULE,

	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,

	NGX_MODULE_V1_PADDING	// 填充

};

编写config文件

创建:

touch config

内容:

ngx_addon_name=ngx_http_fly_filter_module
HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES ngx_http_fly_filter_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_filter_module.c"

包含三部分信息,一个是模块的名称这里名称需要与代码中的定义的模块名称ngx_module_t一致;第二部分是指定模块的类型和名称,这里定义的是一个filter模块;最后是指定模块源文件路径。
注意,config文件要和模块的代码在相同目录。

编译模块到Nginx源码中

(1)配置中添加模块:

./configure --prefix=/usr/local/nginx --with-http_realip_module 
--with-http_addition_module --with-http_gzip_static_module 
--with-http_secure_link_module --with-http_stub_status_module 
--with-stream --with-pcre=/home/fly/workspace/pcre-8.41 
--with-zlib=/home/fly/workspace/zlib-1.2.11 
--with-openssl=/home/fly/workspace/openssl-1.1.0g 
--add-module=/mnt/hgfs/sourcecode_learning/ngx_http_filter_module

注意模块路径要正确。出现如下表示成功:

configuring additional modules
adding module in /mnt/hgfs/sourcecode_learning/ngx_http_filter_module
 + ngx_http_fly_filter_module was configured
creating objs/Makefile

(2)查看是否添加模块到动态代码中:

vim objs/ngx_modules.c

(3)编译:

make
sudo make install

执行效果

编译安装完成后,在conf文件中添加模块的开关(predix on):

worker_processes 4;

events {
	worker_connections 1024;
}

http {

	upstream backend {
		server 192.168.7.146:8889;
		server 192.168.7.146:8890;
	}

	server {
		listen 8888;
		location / {
			proxy_pass http://backend;
		}
	}
	server {
                listen 8889;
        }
	server {
                listen 8890;
		predix on;
        }
	server {
                listen 8891;
        }
}

执行Nginx:

sudo /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/fly.conf 

在网页输入IP和端口,执行效果如下:
在这里插入图片描述
可以看到,返回的网页中多出来添加的内容(FLY.)。

总结

  1. Nginx中http模块非常多,每个模块都会有ngx_http_module_t,为了防止解析过程出现冲突,Nginx编译的时候会把所有的模块都集中起来,组织到/obj/ngx_module.c(以数组的方式)。
  2. 在编译模块时,需要编写config文件,这个文件最好不要使用笔记本编辑(notepad),容易造成编码方式的错误。
  3. 网页中常见的广告(其实里面存储了图片、链接、名称信息)等等其实就是通过nginx过滤器模块去实现的。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>