【中间件开发】Nginx中过滤器模块实现
文章目录
- 前言
- 一、Nginx组件开发
- 1.1 组件如何使用?
- 二、过滤器模块实现
- 2.1 相关Nginx源码解析
- 2.1.1 ngx_command_t
- 2.1.2 ngx_http_module_t
- 2.1.3 ngx_module_s
- 2.2 过滤器模块设计
- 2.2.1 完成模块编写中问题
- 三、handler模块实现
- 3.1 handler流程实现
- 3.2 多进程的slab
- 总结
前言
本文首先介绍了的Nginx的组件以及使用,并实现了一个filter模块。
一、Nginx组件开发
上一节中我们知道了Nginx的基本概念,在本节中我们将利用Nginx进行相对应的组件开发。
- 首先明确Nginx可以进行哪些组件开发
三个模块开发
- upstream(两条黑线箭头)
- 负载均衡模块
- filter(红线,可以用来加一些广告等)
- 对响应内容进行增加等
- handle(黑加红,黑加红)
- 处理响应内容,例如对发起请求的ip地址进行计数
1.1 组件如何使用?
我们完成了组件,怎样将这个组件添加到Nginx中?
- config文件+原文件(ngx_http_prefix_filter_module.c)编写
- 注意windows中编写的config不能直接用于Linux中,会有字符编码问题
# config文件
ngx_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"
- 重新编译Nginx,注意最后一句,会将我们的模块文件信息放入Nginx编译环境中。
./configure --prefix=/usr/local/nginx --with-http_realip_module --with-http_addition_module --with-http_ssl_module --with-http_gzip_static_module --with-http_secure_link_module --with-http_stub_status_module --with-stream --with-pcre=/root/install_package/nginx/pcre-8.45 --with-zlib=/root/install_package/nginx/zlib-1.2.13 --with-openssl=/root/install_package/nginx/openssl-1.1.1s --add-module=/root/install_package/nginx/ngx_code_test/ngx_http_prefix_filter_module/
make && make install
- 将实现的功能在nginx.conf中进行描述
二、过滤器模块实现
2.1 相关Nginx源码解析
2.1.1 ngx_command_t
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; // 一般为NULL
};
2.1.2 ngx_http_module_t
typedef struct {
// 在解析配置文件中http{}配置块前调用
ngx_int_t (*preconfiguration)(ngx_conf_t *cf);
// 在解析配置文件中http{}配置块后调用
ngx_int_t (*postconfiguration)(ngx_conf_t *cf);
// 创建http模块的main config
void *(*create_main_conf)(ngx_conf_t *cf);
// 初始化http模块的main config
char *(*init_main_conf)(ngx_conf_t *cf, void *conf);
// 创建http模块的server config
void *(*create_srv_conf)(ngx_conf_t *cf);
// 合并http模块的server config,用于实现server config到main config的指令的继承、覆盖
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
// 创建http模块的location config
void *(*create_loc_conf)(ngx_conf_t *cf);
// 合并http模块的location config,用于实现location config到server config的指令的继承、覆盖
char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;
2.1.3 ngx_module_s
struct ngx_module_s {
ngx_uint_t ctx_index; // 是该模块在同一类模块中的序号
ngx_uint_t index; // 该模块在所有Nginx模块中的序号, 即在ngx_modules数组里的唯一索引
char *name; // 模块的名字,用NGX_MODULE_V1初始化为NULL
ngx_uint_t spare0; //保留字段,用NGX_MODULE_V1初始化为0
ngx_uint_t spare1;
ngx_uint_t version; // 版本号
const char *signature;
void *ctx; // ctx为ngx_module_s与各个模块的纽带,也可以说是具体模块的公共接口。
ngx_command_t *commands; // 模块支持的指令,数组形式,最后用空对象表示结束
ngx_uint_t type;
// 以下7个函数指针,俗称钩子函数,会在程序启动、结束等不同阶段被调用
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);
// 下面八个预留字段,用NGX_MODULE_V1_PADDING宏全部初始化为0
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;
};
2.2 过滤器模块设计
我们的目的是在返回的网页中添加额外的内容。
所以第一步是设计对应的指令add_prefix
。
typedef struct {
ngx_flag_t enable;
} ngx_http_prefix_filter_conf_t;
static ngx_command_t ngx_http_prefix_filter_commands[] = {
{
ngx_string("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,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_prefix_filter_conf_t, enable),
NULL
},
ngx_null_command
};
第二步:在对应的执行位置填充相关的函数
进入location调用create,遇到add_prefix
调用ngx_conf_set_flag_slot,当location返回调用merge
static ngx_http_module_t ngx_http_prefix_filter_module_ctx = {
NULL, // conf
ngx_http_prefix_filter_init, // 将写的该filter模块采用头插法加入到链表中,在这个函数中我们可以将想要加入的内容写到head和body
NULL, // main
NULL,
NULL, // server
NULL,
ngx_http_prefix_filter_create_conf, // loc
ngx_http_prefix_filter_merge_conf
};
第三步:完成模块以及各种信息的拼接
ngx_module_t ngx_http_prefix_filter_module = {
NGX_MODULE_V1,
&ngx_http_prefix_filter_module_ctx,
ngx_http_prefix_filter_commands, // 循环遍历读取指令
NGX_HTTP_MODULE,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NGX_MODULE_V1_PADDING
};
第四步:完成具体的函数。
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_int_t ngx_http_prefix_filter_init(ngx_conf_t *cf) {
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;
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);
}
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);
}
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;
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;
ngx_chain_t *cl = ngx_alloc_chain_link(r->pool);
cl->buf = b;
cl->next = in;
return ngx_http_next_body_filter(r, cl);
}
2.2.1 完成模块编写中问题
config文件需要在Linux环境下编写,不然会出现^M
字符多余。
三、handler模块实现
3.1 handler流程实现
- nginx启动流程
- nginx中conf文件的功能开启
- 当HTTP请求来的时候
3.2 多进程的slab
采用共享内存来实现多进程之间的资源共享。
总结
本文介绍了Nginx组件的使用以及filter模块的开发。Handler模块也是同样的流程,只是利用这一套流程实现了不同的功能
参考链接:
https://github.com/0voice
https://zhuanlan.zhihu.com/p/410743061
https://blog.csdn.net/chosen0ne/article/details/7730292