- 一、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、完整代码
比如下面的html,在index.html中并没有蓝框中的那部分,这部分是哪来的呢?
这是通过http模块来实现的,后续如果要对一批网页都进行添加这个内容,依次都去修改html比较麻烦,通过模块化的方式,只要配置conf文件即可。
这些公共的部分,可以在nginx内做成一个模块。
模块不仅可以实现上面这种方式,还可以在客户端可以在发送请求后,接受到html,通过md5,用来验证接受到的网页是否正确
按请求响应来划分,在Nginx中有三种模块:
- 两根红线(浏览器->Nginx->服务器):upstream模块
- 两根绿线(服务器->Nginx->浏览器):Filter模块
- 一红一绿(浏览器->Nginx->浏览器):handler模块
应用:比如说Nginx受到攻击,可以通过handler模块去处理
二、模块的基本结构 ngx_module_sstruct 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模块
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:配置指令的执行函数
http_module不是用来存属性的,只负责去解析conf文件
HTTP block中的配置结构主要分为3中,main、server{}、location{}
提供了4对(8个)回调函数,这些函数的执行顺序如下图所示
要实现的功能,就是像下图那样,蓝色框的那部分,没有添加在html里面,但是可以在conf配置中,添加add_header命令,就可以实现下面的功能
//模块支持的指令 数组
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)//这个是自带的,不需要实现
按照执行顺序
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;
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;
}
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
如果当前层没有设置,上一层设置了,那么就给上一层设置的值
也就是说,如果上一层有,想沿用上一层的设置,当前层设置不设置都无所谓
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("Author : King0voice
");
//通过自己的实现的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
#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_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); }



