当前位置: 首页 > article >正文

【中间件开发】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可以进行哪些组件开发

三个模块开发

  1. upstream(两条黑线箭头)
    1. 负载均衡模块
  2. filter(红线,可以用来加一些广告等)
    1. 对响应内容进行增加等
  3. handle(黑加红,黑加红)
    1. 处理响应内容,例如对发起请求的ip地址进行计数

image.png

1.1 组件如何使用?

我们完成了组件,怎样将这个组件添加到Nginx中?

  1. config文件+原文件(ngx_http_prefix_filter_module.c)编写
    1. 注意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"
  1. 重新编译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/
  1. make && make install
  2. 将实现的功能在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 完成模块编写中问题

image.png

config文件需要在Linux环境下编写,不然会出现^M字符多余。

三、handler模块实现

3.1 handler流程实现

  1. nginx启动流程
  2. nginx中conf文件的功能开启
  3. 当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


http://www.kler.cn/a/429300.html

相关文章:

  • Oracle 批量投入数据方法总结
  • 镭速大文件传输视频文件预览实现原理
  • 2025第3周 | json-server的基本使用
  • Linux安装Docker教程(详解)
  • Unity ShaderGraph中Lit转换成URP的LitShader
  • 01.02、判定是否互为字符重排
  • MFC 自定义静态文本控件:增强型标签控件
  • 40分钟学 Go 语言高并发:负载均衡与服务治理
  • 【前端】全面解析 JavaScript 中的 this 指向规则
  • 二阶段nginx1.0
  • 一些好用的网站
  • 深入理解Linux进程管理机制
  • 服务器数据恢复—LINUX下各文件系统删除/格式化的数据恢复可行性分析
  • java:commons-configuration2读取yaml及组合配置定义(CombinedConfiguration)
  • 华为:LLM工具调用数据合成方法
  • 开源向量数据库介绍说明
  • LearnOpenGL学习(高级OpenGL --> 帧缓冲,立方体贴图,高级数据)
  • 策略模式实战 - 猜拳游戏
  • 如何配置Jackson以忽略Java类中为null或空(empty)的字段
  • 避大坑!Vue3中reactive丢失响应式的问题
  • guava 整合springboot 自定义注解实现接口鉴权调用保护
  • 题海拾贝:力扣 231. 2 的幂
  • 使用Python和OpenGL实现3D立方体的交互式显示
  • 康托展开和逆康托展开
  • java-数组—acwing
  • 【C语言】数据库事物的ACID属性