> 文档中心 > Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现

Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现

Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现

  • 一、Nignx中的模块是什么?
  • 二、模块的基本结构
    • `ngx_module_s`
    • `ngx_command_s`
    • `ngx_http_module_t`
  • 三、实现filter模块
    • 1、定义模块指令数组、ngx_http模块、ngx模块
    • 2、`ngx_http_prefix_filter_create_conf`
    • 3、`ngx_conf_set_flag_slot`(nginx已经实现的)
    • 5、`ngx_http_prefix_filter_merge_conf`
    • 6、`ngx_http_prefix_filter_init`
      • 1)`ngx_http_prefix_filter_header_filter`
      • 2)`ngx_http_prefix_filter_body_filter`
  • 四、模块config
  • 五、编译模块
  • 六、附录:
    • 1、命令的配置位置是什么意思
    • 2、完整代码

参考链接

一、Nignx中的模块是什么?

比如下面的html,在index.html中并没有蓝框中的那部分,这部分是哪来的呢?
这是通过http模块来实现的,后续如果要对一批网页都进行添加这个内容,依次都去修改html比较麻烦,通过模块化的方式,只要配置conf文件即可。
Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现
这些公共的部分,可以在nginx内做成一个模块。
模块不仅可以实现上面这种方式,还可以在客户端可以在发送请求后,接受到html,通过md5,用来验证接受到的网页是否正确

Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现

按请求响应来划分,在Nginx中有三种模块:

  • 两根红线(浏览器->Nginx->服务器):upstream模块
  • 两根绿线(服务器->Nginx->浏览器):Filter模块
  • 一红一绿(浏览器->Nginx->浏览器):handler模块

应用:比如说Nginx受到攻击,可以通过handler模块去处理

二、模块的基本结构

ngx_module_s

struct ngx_module_s {    ngx_uint_t     ctx_index;//同一类模块中的序号    ngx_uint_t     index;//在所有模块中的序号    char   *name;//模块的名字    ngx_uint_t     spare0;//保留字段    ngx_uint_t     spare1;//保留字段    ngx_uint_t     version;//版本号    const char    *signature;//签名//上面字段可以用宏NGX_MODULE_V1进行初始化(如果不太关心上面内容的话)    void   *ctx;//具体的公共模块接口    ngx_command_t *commands;//模块支持的指令    ngx_uint_t     type;//模块的标识类型//以下为hook函数    ngx_int_t    (*init_master)(ngx_log_t *log);//主进程master初始化时调用    ngx_int_t    (*init_module)(ngx_cycle_t *cycle);//模块初始化时调用    ngx_int_t    (*init_process)(ngx_cycle_t *cycle);//工作进程worker初始化时调用    ngx_int_t    (*init_thread)(ngx_cycle_t *cycle);//线程初始化时调用    void  (*exit_thread)(ngx_cycle_t *cycle);//退出线程时调用    void  (*exit_process)(ngx_cycle_t *cycle);//退出工作进程worker时调用    void  (*exit_master)(ngx_cycle_t *cycle);//退出主进程时调用    //8个预留字段,可以通过宏NGX_MODULE_V1_PADDING进行初始化    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;};

初始化ngx_module_s时,用到的宏

#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

ngx_module_s中较为核心的是以下三个内容

  • ctx:具体的公共模块接口
           需要自定义一个具体的模块,比如ngx_http_module_t,要对应type中的模块标识
  • commands:模块支持的指令
           需要自定义ngx_command_t
  • type:模块的标识类型
            NGX_CORE_MODULE     核心模块
            NGX_CONF_MODULE     配置模块
            NGX_EVENT_MODULE   event模块
            NGX_HTTP_MODULE     http模块
            NGX_MAIL_MODULE      mail模块

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

比较重要的参数

  • name:配置指令名称
    比如:ngx_string(“count”)表示conf配置文件中的可以输入的命令count
  • type:配置指令类型
    type主要是一些标志位,比如命令的配置位置(本文最后附录有解释),以及命令的参数数目等
  • set:配置指令的执行函数

ngx_http_module_t

http_module不是用来存属性的,只负责去解析conf文件
HTTP block中的配置结构主要分为3中,main、server{}、location{}
提供了4对(8个)回调函数,这些函数的执行顺序如下图所示
Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现

三、实现filter模块

要实现的功能,就是像下图那样,蓝色框的那部分,没有添加在html里面,但是可以在conf配置中,添加add_header命令,就可以实现下面的功能
Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现

1、定义模块指令数组、ngx_http模块、ngx模块

//模块支持的指令 数组static ngx_command_t ngx_http_prefix_filter_commands[] = {{ngx_string("add_prefix"),//自定义的http模块指令为add_prefixNGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_FLAG,//标志位ngx_conf_set_flag_slot,//(4)//解析命令的参数,这个函数是自带的,用于解析on和offNGX_HTTP_LOC_CONF_OFFSET,//指令存储地址offsetof(ngx_http_prefix_filter_conf_t, enable),//指令存储地址的偏移量NULL},ngx_null_command};//具体的模块信息:http模块,并配置它的回调函数static ngx_http_module_t ngx_http_prefix_filter_module_ctx = {NULL,ngx_http_prefix_filter_init,//(1)//解析完conf要做的事,用于设置运行时的参数,也就是可以去初始化http_prefix_filter,NULL,NULL,NULL,NULL,ngx_http_prefix_filter_create_conf,//(2)//创建一块内存,用于存放配置信息ngx_http_prefix_filter_merge_conf//(3)//合并配置信息};//回调函数的执行顺序是  2--(4)--3--1     如果conf中没有调用指令,就不会有(4)//主模块,也就是将上面的http模块和commands整合到一起ngx_module_t ngx_http_prefix_filter_module = {NGX_MODULE_V1,//宏&ngx_http_prefix_filter_module_ctx,//具体的模块  http模块ngx_http_prefix_filter_commands,//模块支持的指令NGX_HTTP_MODULE,//模块类型为http模块NULL,NULL,NULL,NULL,NULL,NULL,NULL,NGX_MODULE_V1_PADDING//宏}; 

根据上述的模块配置信息,也就是说要去实现以下四个函数

static ngx_int_t ngx_http_prefix_filter_init(ngx_conf_t *cf)static void *ngx_http_prefix_filter_create_conf(ngx_conf_t *cf)static char *ngx_http_prefix_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)char *ngx_conf_set_flag_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)//这个是自带的,不需要实现

以下按照执行顺序来以此解释

1.要执行自己定义的add_prefix参数,那么就需要定义一个结构体去接受参数
2.接受参数前,首先要进参数结构体的内存空间的分配,也就是在ngx_http_prefix_filter_create_conf中进行。
3.创建完这保存参数的结构体后,就需要把参数解析出来,给这个结构体赋值,在ngx_conf_set_flag_slot中进行
4.获得结构体参数后,接下去就可以去使用这个参数,去实现自己的功能了。系统会自动调用ngx_http_top_header_filterngx_http_top_body_filter这两个函数,那么如何去添加自己想要的功能呢?这里可以使用一个hook的方法,通过临时变量存储默认调用的ngx_http_top_header_filterngx_http_top_body_filter函数。ngx_http_top_header_filter指向自己实现的函数,在自己实现的函数里面再去调用默认的ngx_http_top_header_filter,来实现一个hook的功能。
5.另外,为了保证header和body中数据的一致,比如header中的数据长度和body中 数据要对应起来。可以对当前模块设置一个ctx,对header和body对应的函数之间的通信,保证一致性。

2、ngx_http_prefix_filter_create_conf

存储该函数的函数指针

 char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);

这部分主要是用于分配存储参数的结构体的空间(通过内存池来分配)

static void *ngx_http_prefix_filter_create_conf(ngx_conf_t *cf) {//主要是分配空间ngx_http_prefix_filter_conf_t *conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_prefix_filter_conf_t));//从内存池中拿出一块内存出来if (conf == NULL) {return NULL;}conf->enable = NGX_CONF_UNSET;return conf;}

ngx_http_prefix_filter_conf_t这个是用于存储命令参数的结构体

typedef struct {ngx_flag_t enable;} ngx_http_prefix_filter_conf_t;

现在只是将该参数的结构体定义完了,可以看到这个函数,分配完空间后,将它返回了,那么哪里去接受这个conf呢?
Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现

3、ngx_conf_set_flag_slot(nginx已经实现的)

这个函数是nginx自己实现的,用来解析参数中的on和off

char *ngx_conf_set_flag_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){    char  *p = conf;    ngx_str_t *value;    ngx_flag_t*fp;    ngx_conf_post_t  *post;    fp = (ngx_flag_t *) (p + cmd->offset);    if (*fp != NGX_CONF_UNSET) { return "is duplicate";    }    value = cf->args->elts;    if (ngx_strcasecmp(value[1].data, (u_char *) "on") == 0) { *fp = 1;    } else if (ngx_strcasecmp(value[1].data, (u_char *) "off") == 0) { *fp = 0;    } else { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,"invalid value \"%s\" in \"%s\" directive, ""it must be \"on\" or \"off\"",value[1].data, cmd->name.data); return NGX_CONF_ERROR;    }    if (cmd->post) { post = cmd->post; return post->post_handler(cf, post, fp);    }    return NGX_CONF_OK;}

这里的红箭头指向的void* conf参数就是ngx_http_prefix_filter_create_conf中返回的confngx_http_prefix_filter_create_conf中只分配了一块内存,还没有进行赋值
Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现
当解析的参数为"on",那么下面结构体中enable的值就为1
当解析的参数为"off",那么下面结构体中enable的值就为0
Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现

5、ngx_http_prefix_filter_merge_conf

存储该函数的函数指针

char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);

主要是用于给位置处于location下的 add_header指令,根据上一层的指令给值。

static char *ngx_http_prefix_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child) {ngx_http_prefix_filter_conf_t *prev = (ngx_http_prefix_filter_conf_t*)parent;ngx_http_prefix_filter_conf_t *conf = (ngx_http_prefix_filter_conf_t*)child;ngx_conf_merge_value(conf->enable, prev->enable, 0);return NGX_CONF_OK;}

merge做了什么事?

比如下图的conf文件,有多个add_header指令(自定义的),用于给当前的配置位置的命令传输值。
比如location下如果没有定义add_header,那么就会沿用上一层的add_header的命令。
Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现

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

如果当前层没有设置,上一层也没有设置,那么就给默认值default
如果当前层没有设置,上一层设置了,那么就给上一层设置的值

也就是说,如果上一层有,想沿用上一层的设置,当前层设置不设置都无所谓
Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现

6、ngx_http_prefix_filter_init

ngx_http_top_header_filterngx_http_top_body_filter是nginx对header和body的filter,是nginx中默认实现的,并且在回应请求时,会调用。

现在要实现自己的filter(比如网页,头部插入固定的模块内容),就要自己去实现功能。这里采用头插法,对nginx自带的http_filter函数用两个函数指针ngx_http_next_header_filterngx_http_next_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_prefix_filter_init(ngx_conf_t *cf) {//(用自己实现的替换原先的,然后在自己实现的里面调用 原先的),  //采用头插法加入header和body的两个filter模块    这些模块都是http请求后,响应时处理的模块ngx_http_next_header_filter = ngx_http_top_header_filter;ngx_http_top_header_filter = ngx_http_prefix_filter_header_filter;ngx_http_next_body_filter = ngx_http_top_body_filter;ngx_http_top_body_filter = ngx_http_prefix_filter_body_filter;//把自己实现的filter,作为top,在这个函数里面再调用ngx_http_next_body_filterreturn NGX_OK;}

1)ngx_http_prefix_filter_header_filter

主要是为了做下面的步骤,由于body中要多传输一部分数据,那么header中就要对应增加相应的长度

r->headers_out.content_length_n += filter_prefix.len

但是如果出现,header中的长度和body中的数据长度不一致,那么就会出现问题,为了确保数据的一致性,那么就可以通过在filter_header中ngx_http_set_ctx来设置ctx,在filter_body中来ngx_http_get_module_ctx,保证数据的一致。
并且在filte_header中添加一些判断,保证不出错。

最后再把其他处理过程交给ngx_http_prefix_filter_header_filter,否则最后的网页,只有新添加的模块内容,原来的其他内容就没了。

static ngx_int_t ngx_http_prefix_filter_header_filter(ngx_http_request_t *r) {ngx_http_prefix_filter_ctx_t *ctx;ngx_http_prefix_filter_conf_t *conf;if (r->headers_out.status != NGX_HTTP_OK) {return ngx_http_next_header_filter(r);}ctx = ngx_http_get_module_ctx(r, ngx_http_prefix_filter_module);if (ctx) {return ngx_http_next_header_filter(r);}conf = ngx_http_get_module_loc_conf(r, ngx_http_prefix_filter_module);if (conf == NULL) {return ngx_http_next_header_filter(r);}if (conf->enable == 0) {return ngx_http_next_header_filter(r);}//在这里设置ngx_http_set_ctx,在下面body部分可以ngx_http_get_module_ctx来获得ctx,相当于是存储了一个通信的资源,以此来保证header和body的数据的一致性,不然可能出现header中的r->headers_out.content_length_n和下面body新添加的数据不对应ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_prefix_filter_ctx_t));if (ctx == NULL) {return NGX_ERROR;}ctx->add_prefix = 0;ngx_http_set_ctx(r, ctx, ngx_http_prefix_filter_module);if (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) {ctx->add_prefix = 1;if (r->headers_out.content_length_n > 0) {r->headers_out.content_length_n += filter_prefix.len;}}return ngx_http_prefix_filter_header_filter(r);}

2)ngx_http_prefix_filter_body_filter

这部分主要是创建一块buffer内存,赋值模块的内容,然后接到 链表上,就是全部的数据内容了。然后交给ngx_http_next_body_filter(r, cl)去处理。

static ngx_str_t filter_prefix = ngx_string("

Author : King

0voice

"
);//通过自己的实现的filter,加入自己要实现的部分,然后调用原来的ngx_http_next_body_filter就行了//后续http响应,处理过程就会调用这个filter模块。自己的部分+原先的部分(原先部分通过调用ngx_http_next_body_filter)static ngx_int_t ngx_http_prefix_filter_body_filter(ngx_http_request_t *r, ngx_chain_t *in) {ngx_http_prefix_filter_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_prefix_filter_module);if (ctx == NULL || ctx->add_prefix != 1) {return ngx_http_next_body_filter(r, in);}ctx->add_prefix = 2;//创建一块buffer(从内存池取)(用于存储字符)并添加数据 html中的数据都是存在buffer中的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;//创建一个链的节点,分配buffer,并且插入链表的头部ngx_chain_t *cl = ngx_alloc_chain_link(r->pool);//chain是链的节点, 链由若干个buffer组成(这块内存什么时候释放?连接池释放的时候才会释放)cl->buf = b;cl->next = in;return ngx_http_next_body_filter(r, cl);}

四、模块config

gx_addon_name=ngx_http_prefix_filter_moduleHTTP_FILTER_MODULES="$HTTP_FILTER_MODULES ngx_http_prefix_filter_module"NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_prefix_filter_module.c"
  • ngx_addon_name:添加的模块名称
  • HTTP_MODULES:所有http模块
  • NGX_ADDON_SRCS:所有要编译的.c源文件

config中添加模块的方式有点像添加环境变量的方式

五、编译模块

./configure去生成可以的编译文件

./configure --add-module=/home/xuheding/share/Open_source_code/nginx-1.21.6/ngx_http_prefix_filter_module

执行完后,可以看到新加模块已经被生成可编译文件的一部分了
Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现

在configure生成的参与编译的objs/ngx_modules.c文件中,可以找到新添加的模块
Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现

后续通过make就可以进行编译了

六、附录:

1、命令的配置位置是什么意思

type主要是一些标志位,比如命令的作用域,以及命令的传参类型等…
Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现
NGX_HTTP_LOC_CONF表示什么意思呢?

先看看下面,表示一些命令所在conf文件中的作用位置
Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现
比如设置程成NGX_HTTP_MAIN_CONF表示,该命令作用于下图http中,也就是在http大括号内,可以输入命令
同理,NGX_HTTP_SRV_CONF表示,在http中的server中,可以输入的命令
NGX_HTTP_LOC_CONF表示在,http中的server的location中,可以输入的命令
Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现
也就是说这些标志位决定命令的作用域

那么NGX_CONF_NOARGS是什么意思?表示配置指令不接受任何参数
用的比较多的还有NGX_CONF_FLAG,表示后面参数要么是on或者off

2、完整代码

#include #include #include typedef struct {ngx_flag_t enable;} ngx_http_prefix_filter_conf_t;typedef struct {ngx_int_t add_prefix;} ngx_http_prefix_filter_ctx_t;static ngx_int_t ngx_http_prefix_filter_init(ngx_conf_t *cf);static ngx_int_t ngx_http_prefix_filter_header_filter(ngx_http_request_t *r);static ngx_int_t ngx_http_prefix_filter_body_filter(ngx_http_request_t *r, ngx_chain_t *in);static ngx_str_t filter_prefix = ngx_string("

Author : King

0voice

"
);static void *ngx_http_prefix_filter_create_conf(ngx_conf_t *cf) {//主要是分配空间ngx_http_prefix_filter_conf_t *conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_prefix_filter_conf_t));//从内存池中拿出一块内存出来if (conf == NULL) {return NULL;}conf->enable = NGX_CONF_UNSET;return conf;}static char *ngx_http_prefix_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child) {ngx_http_prefix_filter_conf_t *prev = (ngx_http_prefix_filter_conf_t*)parent;ngx_http_prefix_filter_conf_t *conf = (ngx_http_prefix_filter_conf_t*)child;ngx_conf_merge_value(conf->enable, prev->enable, 0);return NGX_CONF_OK;}//模块支持的指令 数组static ngx_command_t ngx_http_prefix_filter_commands[] = {{ngx_string("add_prefix"),//自定义的http模块指令为add_prefixNGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_FLAG,//标志位ngx_conf_set_flag_slot,//(4)//解析命令的参数,这个函数是自带的,用于解析on和offNGX_HTTP_LOC_CONF_OFFSET,//指令存储地址offsetof(ngx_http_prefix_filter_conf_t, enable),//指令存储地址的偏移量NULL},ngx_null_command};//具体的模块信息:http模块,并配置它的回调函数static ngx_http_module_t ngx_http_prefix_filter_module_ctx = {NULL,ngx_http_prefix_filter_init,//(1)//解析完conf要做的事,用于设置运行时的参数,也就是可以去初始化http_prefix_filter,NULL,NULL,NULL,NULL,ngx_http_prefix_filter_create_conf,//(2)//创建一块内存,用于存放配置信息ngx_http_prefix_filter_merge_conf//(3)//合并配置信息};//回调函数的执行顺序是 2--(4)--3--1 如果conf中没有调用指令,就不会有(4)//主模块,也就是将上面的http模块和commands整合到一起ngx_module_t ngx_http_prefix_filter_module = {NGX_MODULE_V1,//宏&ngx_http_prefix_filter_module_ctx,//具体的模块 http模块ngx_http_prefix_filter_commands,//模块支持的指令NGX_HTTP_MODULE,//模块类型为http模块NULL,NULL,NULL,NULL,NULL,NULL,NULL,NGX_MODULE_V1_PADDING//宏}; 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_prefix_filter_init(ngx_conf_t *cf) {//(用自己实现的替换原先的,然后在自己实现的里面调用 原先的), //采用头插法加入header和body的两个filter模块 这些模块都是http请求后,响应时处理的模块ngx_http_next_header_filter = ngx_http_top_header_filter;ngx_http_top_header_filter = ngx_http_prefix_filter_header_filter;ngx_http_next_body_filter = ngx_http_top_body_filter;ngx_http_top_body_filter = ngx_http_prefix_filter_body_filter;//把自己实现的filter,作为top,在这个函数里面再调用ngx_http_next_body_filterreturn NGX_OK;}static ngx_int_t ngx_http_prefix_filter_header_filter(ngx_http_request_t *r) {ngx_http_prefix_filter_ctx_t *ctx;ngx_http_prefix_filter_conf_t *conf;if (r->headers_out.status != NGX_HTTP_OK) {return ngx_http_next_header_filter(r);}ctx = ngx_http_get_module_ctx(r, ngx_http_prefix_filter_module);if (ctx) {return ngx_http_next_header_filter(r);}conf = ngx_http_get_module_loc_conf(r, ngx_http_prefix_filter_module);if (conf == NULL) {return ngx_http_next_header_filter(r);}if (conf->enable == 0) {return ngx_http_next_header_filter(r);}//在这里设置ngx_http_set_ctx,在下面body部分可以ngx_http_get_module_ctx来获得ctx,相当于是存储了一个通信的资源,以此来保证header和body的数据的一致性,不然可能出现header中的r->headers_out.content_length_n和下面body新添加的数据不对应ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_prefix_filter_ctx_t));if (ctx == NULL) {return NGX_ERROR;}ctx->add_prefix = 0;ngx_http_set_ctx(r, ctx, ngx_http_prefix_filter_module);if (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) {ctx->add_prefix = 1;if (r->headers_out.content_length_n > 0) {r->headers_out.content_length_n += filter_prefix.len;}}return ngx_http_prefix_filter_header_filter(r);}//通过自己的实现的filter,加入自己要实现的部分,然后调用原来的ngx_http_next_body_filter就行了//后续http响应,处理过程就会调用这个filter模块。自己的部分+原先的部分(原先部分通过调用ngx_http_next_body_filter)static ngx_int_t ngx_http_prefix_filter_body_filter(ngx_http_request_t *r, ngx_chain_t *in) {ngx_http_prefix_filter_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_prefix_filter_module);if (ctx == NULL || ctx->add_prefix != 1) {return ngx_http_next_body_filter(r, in);}ctx->add_prefix = 2;//创建一块buffer(从内存池取)(用于存储字符)并添加数据 html中的数据都是存在buffer中的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;//创建一个链的节点,分配buffer,并且插入链表的头部ngx_chain_t *cl = ngx_alloc_chain_link(r->pool);//chain是链的节点, 链由若干个buffer组成(这块内存什么时候释放?连接池释放的时候才会释放)cl->buf = b;cl->next = in;return ngx_http_next_body_filter(r, cl);}