一、Nignx中的模块是什么?
比如下面的html,在index.html中并没有蓝框中的那部分,这部分是哪来的呢?
这是通过http模块来实现的,后续如果要对一批网页都进行添加这个内容,依次都去修改html比较麻烦,通过模块化的方式,只要配置conf文件即可。
这些公共的部分,可以在nginx内做成一个模块。
模块不仅可以实现上面这种方式,还可以在客户端可以在发送请求后,接受到html,通过md5,用来验证接受到的网页是否正确
按请求响应来划分,在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_ttype
:模块的标识类型
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配置文件中的可以输入的命令counttype
:配置指令类型
type主要是一些标志位,比如命令的配置位置(本文最后附录有解释),以及命令的参数数目等set
:配置指令的执行函数
ngx_http_module_t
http_module不是用来存属性的,只负责去解析conf文件
HTTP block中的配置结构主要分为3中,main、server{}、location{}
提供了4对(8个)回调函数,这些函数的执行顺序如下图所示
三、实现filter模块
要实现的功能,就是像下图那样,蓝色框的那部分,没有添加在html里面,但是可以在conf配置中,添加add_header命令,就可以实现下面的功能
1、定义模块指令数组、ngx_http模块、ngx模块
//模块支持的指令 数组 static ngx_command_t ngx_http_prefix_filter_commands[] = { { ngx_string("add_prefix"),//自定义的http模块指令为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,//(4)//解析命令的参数,这个函数是自带的,用于解析on和off NGX_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_filter
和ngx_http_top_body_filter
这两个函数,那么如何去添加自己想要的功能呢?这里可以使用一个hook的方法,通过临时变量存储默认调用的ngx_http_top_header_filter
和ngx_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呢?
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
中返回的conf
,ngx_http_prefix_filter_create_conf
中只分配了一块内存,还没有进行赋值
当解析的参数为"on",那么下面结构体中enable的值就为1
当解析的参数为"off",那么下面结构体中enable的值就为0
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的命令。
#define ngx_conf_merge_value(conf, prev, default) \ if (conf == NGX_CONF_UNSET) { \ conf = (prev == NGX_CONF_UNSET) ? default : prev; \ }
如果当前层没有设置,上一层也没有设置,那么就给默认值default
如果当前层没有设置,上一层设置了,那么就给上一层设置的值
也就是说,如果上一层有,想沿用上一层的设置,当前层设置不设置都无所谓
6、ngx_http_prefix_filter_init
ngx_http_top_header_filter
和ngx_http_top_body_filter
是nginx对header和body的filter,是nginx中默认实现的,并且在回应请求时,会调用。
现在要实现自己的filter(比如网页,头部插入固定的模块内容),就要自己去实现功能。这里采用头插法,对nginx自带的http_filter函数用两个函数指针ngx_http_next_header_filter
和ngx_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_filter return 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("<h2>Author : King</h2><p><a href=\"http://www.0voice.com\">0voice</a></p>"); //通过自己的实现的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_module HTTP_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
执行完后,可以看到新加模块已经被生成可编译文件的一部分了
在configure生成的参与编译的objs/ngx_modules.c文件中,可以找到新添加的模块
后续通过make就可以进行编译了
六、附录:
1、命令的配置位置是什么意思
type主要是一些标志位,比如命令的作用域,以及命令的传参类型等…
NGX_HTTP_LOC_CONF
表示什么意思呢?
先看看下面,表示一些命令所在conf文件中的作用位置
比如设置程成NGX_HTTP_MAIN_CONF
表示,该命令作用于下图http中,也就是在http大括号内,可以输入命令
同理,NGX_HTTP_SRV_CONF
表示,在http中的server中,可以输入的命令
NGX_HTTP_LOC_CONF
表示在,http中的server的location中,可以输入的命令
也就是说这些标志位决定命令的作用域
那么NGX_CONF_NOARGS
是什么意思?表示配置指令不接受任何参数
用的比较多的还有NGX_CONF_FLAG
,表示后面参数要么是on或者off
2、完整代码
#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> 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("<h2>Author : King</h2><p><a href=\"http://www.0voice.com\">0voice</a></p>"); 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_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,//(4)//解析命令的参数,这个函数是自带的,用于解析on和off NGX_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_filter return 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); }