【中间件开发】Nginx中过滤器模块实现

文章目录

  • 前言
  • 一、Nginx组件开发
    • [1.1 组件如何使用?](#1.1 组件如何使用?)
  • 二、过滤器模块实现
    • [2.1 相关Nginx源码解析](#2.1 相关Nginx源码解析)
      • [2.1.1 ngx_command_t](#2.1.1 ngx_command_t)
      • [2.1.2 ngx_http_module_t](#2.1.2 ngx_http_module_t)
      • [2.1.3 ngx_module_s](#2.1.3 ngx_module_s)
    • [2.2 过滤器模块设计](#2.2 过滤器模块设计)
      • [2.2.1 完成模块编写中问题](#2.2.1 完成模块编写中问题)
  • 三、handler模块实现
    • [3.1 handler流程实现](#3.1 handler流程实现)
    • [3.2 多进程的slab](#3.2 多进程的slab)
  • 总结

前言

本文首先介绍了的Nginx的组件以及使用,并实现了一个filter模块。


一、Nginx组件开发

上一节中我们知道了Nginx的基本概念,在本节中我们将利用Nginx进行相对应的组件开发。

  • 首先明确Nginx可以进行哪些组件开发

三个模块开发

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

1.1 组件如何使用?

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

  1. config文件+原文件(ngx_http_prefix_filter_module.c)编写
    1. 注意windows中编写的config不能直接用于Linux中,会有字符编码问题
conf 复制代码
# 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编译环境中。
shell 复制代码
./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

c 复制代码
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

c 复制代码
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

c 复制代码
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

c 复制代码
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

c 复制代码
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
};

第三步:完成模块以及各种信息的拼接

c 复制代码
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
}; 

第四步:完成具体的函数。

c 复制代码
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流程实现

  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

相关推荐
熬夜苦读学习44 分钟前
Linux文件系统
linux·运维·服务器·开发语言·后端
沐千熏1 小时前
Liunx(CentOS-6-x86_64)系统安装MySql(5.6.50)
linux·mysql·centos
荔枝荷包蛋6661 小时前
【网络】高级IO——Reactor版TCP服务器
运维·服务器
GGGGGGGGGGGGGG.2 小时前
hapxory-ACL基础介绍及案例
运维·服务器·网络
黑牛先生2 小时前
【Linux】匿名管道
linux·运维·服务器
流星白龙2 小时前
【Linux】35.封装 UdpSocket(2)
linux·运维·windows
waicsdn_haha2 小时前
Visual Studio Code 2025 安装与高效配置教程
c语言·ide·windows·vscode·微软·编辑器·win7
是码农没错了2 小时前
银河麒麟系统安装mysql5.7【亲测可行】
linux·运维·kylin
wzhao1013 小时前
WSL进阶使用指南
linux
风静如云3 小时前
OpenBMC:BmcWeb app.run
linux