【Nginx】开发一个HTTP过滤模块
与HTTP处理模块不同。HTTP过滤模块的工作是对发送给用户的HTTP响应做一些加工。
server返回的一个响应能够被随意多个HTTP过滤模块以流水线的方式依次处理。HTTP响应分为头部和包体,ngx_http_send_header和ngx_http_output_filter函数分别负责发送头部和包体。它们会依次调用各个过滤模块对待发送的响应进行处理。
// 过滤模块处理HTTP头部的方法原型
typedef ngx_int_t (*ngx_http_output_header_filter_pt)(ngx_http_request_t *r); // 过滤模块处理HTTP包体的方法原型
typedef ngx_int_t (*ngx_http_output_body_filter_pt)(ngx_http_request_t *r, ngx_chain_t *chain);
// HTTP过滤模块链表入口
extern ngx_http_output_header_filter_pt ngx_http_top_header_filter;
extern ngx_http_output_body_filter_pt ngx_http_top_body_filter;
ngx_int_t
ngx_http_send_header(ngx_http_request_t *r)
{
if (r->header_sent) {
ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
"header already sent");
return NGX_ERROR;
} if (r->err_status) {
r->headers_out.status = r->err_status;
r->headers_out.status_line.len = 0;
} // 从头遍历HTTP头部过滤模块
return ngx_http_top_header_filter(r);
}
ngx_int_t
ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
ngx_int_t rc;
ngx_connection_t *c; c = r->connection; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http output filter \"%V?%V\"", &r->uri, &r->args); // 遍历HTTP包体过滤模块
rc = ngx_http_top_body_filter(r, in); if (rc == NGX_ERROR) {
/* NGX_ERROR may be returned by any filter */
c->error = 1;
} return rc;
}
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_addition_filter_init(ngx_conf_t *cf)
{
// 从头部增加
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_addition_header_filter; // 从头部增加
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_addition_body_filter; return NGX_OK;
}
类似上面ngx_http_addition_filter_init这种初始化函数在什么时候被调用呢?答案是依该方法放在ngx_http_module_t结构体的哪个成员而定。
一般而言,大多数官方HTTP过滤模块通常放在ngx_http_module_t.postconfiguration函数指针中,读取全然部配置项后被回调。各个模块初始化的顺序是怎么样的呢?这由configure命令生成的ngx_modules.c文件里的ngx_modules数组的排列顺序决定。数组中靠前的模块先初始化。因为过滤模块是将自己插入到链表头部,使得ngx_modules数组中过滤模块的排列顺序和它们实际运行的顺序相反。至于ngx_modules数组中的排列顺序,又是由其他脚本决定的。
typedef struct
{
ngx_flag_t enable; // 保存on或者off
} ngx_http_myfilter_conf_t; typedef struct
{
ngx_int_t add_prefix;
} ngx_http_myfilter_ctx_t; // HTTP上下文结构体
这样。当一个请求被拆分成多次处理时。同一个处理函数就行了解该请求已经运行到哪里了,从而接着当前的进度进行处理。
static ngx_http_module_t ngx_http_myfilter_module_ctx =
{
NULL, /* preconfiguration */
ngx_http_myfilter_init, /* postconfiguration */ NULL, /* create_main_conf */
NULL, /* init_main_conf */ NULL, /* create_srv_conf */
NULL, /* merge_srv_conf */ ngx_http_myfilter_create_conf, /* create_loc_conf */
ngx_http_myfilter_merge_conf /* merge_loc_conf */
};
- ngx_http_myfilter_init:初始化该过滤模块。也就是将该模块插入到HTTP过滤模块单链表中,插入方法已经在上面介绍过了。
- ngx_http_myfilter_create_conf:给保存配置项的结构体ngx_http_myfilter_conf_t分配空间并初始化结构体成员
- ngx_http_myfilter_merge_conf:合并出如今main、srv级别的同名配置项
static ngx_command_t ngx_http_myfilter_commands[] =
{
{
ngx_string("myfilter"),
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_myfilter_conf_t, enable),
NULL
},
ngx_null_command
};
首先来看看处理HTTP响应头部的函数:
// 处理请求的头部
static ngx_int_t ngx_http_myfilter_header_filter(ngx_http_request_t *r)
{
ngx_http_myfilter_ctx_t *ctx;
ngx_http_myfilter_conf_t *conf; //假设不是返回成功。这时是不须要理会是否加前缀的,直接交由下一个过滤模块
//处理响应码非200的情形
if (r->headers_out.status != NGX_HTTP_OK)
return ngx_http_next_header_filter(r); //获取http上下文
ctx = ngx_http_get_module_ctx(r, ngx_http_myfilter_module);
if (ctx)
//该请求的上下文已经存在,这说明该模块已经被调用过1次。直接交由下一个过滤模块处理
return ngx_http_next_header_filter(r); //获取存储配置项的ngx_http_myfilter_conf_t结构体
conf = ngx_http_get_module_loc_conf(r, ngx_http_myfilter_module); //假设enable成员为0。也就是配置文件里没有配置add_prefix配置项,
//或者add_prefix配置项的參数值是off,这时直接交由下一个过滤模块处理
if (conf->enable == 0)
return ngx_http_next_header_filter(r); //构造http上下文结构体ngx_http_myfilter_ctx_t
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_myfilter_ctx_t));
if (ctx == NULL)
return NGX_ERROR; //add_prefix为0表示不加前缀
ctx->add_prefix = 0; //将构造的上下文设置到当前请求中
ngx_http_set_ctx(r, ctx, ngx_http_myfilter_module); //myfilter过滤模块仅仅处理Content-Type是"text/plain"类型的http响应
if (r->headers_out.content_type.len >= sizeof("text/plain") - 1
&& ngx_strncasecmp(r->headers_out.content_type.data, (u_char *) "text/plain", sizeof("text/plain") - 1) == 0)
{
ctx->add_prefix = 1; //1表示须要在http包体前增加前缀 //假设处理模块已经在Content-Length写入了http包体的长度,因为
//我们增加了前缀字符串,所以须要把这个字符串的长度也增加到
//Content-Length中
if (r->headers_out.content_length_n > 0)
r->headers_out.content_length_n += filter_prefix.len;
} //交由下一个过滤模块继续处理
return ngx_http_next_header_filter(r);
}
//将在包体中加入这个前缀
static ngx_str_t filter_prefix = ngx_string("~~~~~~~This is a prefix~~~~~~~\n"); // 处理请求的包体
static ngx_int_t ngx_http_myfilter_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
ngx_http_myfilter_ctx_t *ctx; // 获得HTTP上下文
ctx = ngx_http_get_module_ctx(r, ngx_http_myfilter_module); //假设获取不到上下文,或者上下文结构体中的add_prefix为0或者2时,
//都不会加入前缀,这时直接交给下一个http过滤模块处理
if (ctx == NULL || ctx->add_prefix != 1)
return ngx_http_next_body_filter(r, in); //将add_prefix设置为2,这样即使ngx_http_myfilter_body_filter再次回调时。也不会反复加入前缀
ctx->add_prefix = 2; //从请求的内存池中分配内存,用于存储字符串前缀
ngx_buf_t* b = ngx_create_temp_buf(r->pool, filter_prefix.len); //将ngx_buf_t中的指针正确地指向filter_prefix字符串
b->start = b->pos = filter_prefix.data;
b->last = b->pos + filter_prefix.len; //从请求的内存池中生成ngx_chain_t链表,将刚分配的ngx_buf_t设置到
//其buf成员中,并将它加入到原先待发送的http包体前面
ngx_chain_t *cl = ngx_alloc_chain_link(r->pool);
cl->buf = b;
cl->next = in; //调用下一个模块的http包体处理方法。注意这时传入的是新生成的cl链表
return ngx_http_next_body_filter(r, cl);
}
以上函数的流程图例如以下:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbmVzdGxlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
./configure --add-module="/work/nginx/modules/mytest /work/nginx/modules/myfilter"
vim objs/ngx_modules.c
接下来就是make和make install了。成功安装后。我们还要改动配置文件:
vim /usr/local/nginx/conf/nginx.conf
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbmVzdGxlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h> // 配置项结构体
typedef struct {
ngx_flag_t enable;
} ngx_http_myfilter_conf_t; // 上下文结构体
typedef struct {
ngx_int_t add_prefix;
} ngx_http_myfilter_ctx_t; ngx_module_t ngx_http_myfilter_module; // 前向声明 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_str_t filter_prefix = ngx_string("~~~~~~~This is a prefix~~~~~~~\n"); static ngx_int_t ngx_http_myfilter_header_filter(ngx_http_request_t *r)
{
ngx_http_myfilter_ctx_t *ctx;
ngx_http_myfilter_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_myfilter_module);
if (ctx)
return ngx_http_next_header_filter(r); // 上下文已存在,不再处理 conf = ngx_http_get_module_loc_conf(r, ngx_http_myfilter_module); // 获取配置项结构体
if (conf->enable == 0)
return ngx_http_next_header_filter(r); // 此过滤模块未打开 ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_myfilter_ctx_t)); // 创建上下文结构体
if (ctx == NULL)
return NGX_ERROR; ctx->add_prefix = 0; // 0表示不须要加入前缀
ngx_http_set_ctx(r, ctx, ngx_http_myfilter_module); // 仅仅处理Content-Type为text/plain类型的HTTP请求
if (r->headers_out.content_type.len >= sizeof("text/plain")-1 &&
ngx_strncasecmp(r->headers_out.content_type.data, (u_char *)"text/plain", sizeof("text/plain")-1) == 0)
{
ctx->add_prefix = 1; // 1表示须要加入前缀
if (r->headers_out.content_length_n > 0)
r->headers_out.content_length_n += filter_prefix.len; // 响应包体长度添加
} return ngx_http_next_header_filter(r);
} static ngx_int_t ngx_http_myfilter_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
ngx_http_myfilter_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_myfilter_module); // 获取上下文结构体
if (ctx == NULL || ctx->add_prefix != 1)
return ngx_http_next_body_filter(r, in); // 不加入前缀 ctx->add_prefix = 2; // 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); // 跳到下一个过滤模块
} // 初始化HTTP过滤模块
static ngx_int_t ngx_http_myfilter_init(ngx_conf_t *cf)
{
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_myfilter_header_filter; ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_myfilter_body_filter; return NGX_OK;
} // 创建存储配置项的结构体
static void* ngx_http_myfilter_create_conf(ngx_conf_t *cf)
{
ngx_http_myfilter_conf_t *mycf; mycf = (ngx_http_myfilter_conf_t *)ngx_pcalloc(cf->pool, sizeof(ngx_http_myfilter_conf_t));
if (mycf == NULL)
return NULL; mycf->enable = NGX_CONF_UNSET;
return mycf;
} // 合并配置项
static char* ngx_http_myfilter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_myfilter_conf_t *prev = (ngx_http_myfilter_conf_t *)parent;
ngx_http_myfilter_conf_t *conf = (ngx_http_myfilter_conf_t *)child; ngx_conf_merge_value(conf->enable, prev->enable, 0); // 合并函数 return NGX_CONF_OK;
} static ngx_command_t ngx_http_myfilter_commands[] = {
{
ngx_string("myfilter"),
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_myfilter_conf_t, enable),
NULL,
},
ngx_null_command
}; // HTTP框架初始化时调用的八个函数
static ngx_http_module_t ngx_http_myfilter_module_ctx = {
NULL,
ngx_http_myfilter_init,
NULL,
NULL,
NULL,
NULL,
ngx_http_myfilter_create_conf,
ngx_http_myfilter_merge_conf,
}; // 定义一个HTTP模块
ngx_module_t ngx_http_myfilter_module = {
NGX_MODULE_V1, // 0,0,0,0,0,0,1
&ngx_http_myfilter_module_ctx,
ngx_http_myfilter_commands,
NGX_HTTP_MODULE,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NGX_MODULE_V1_PADDING, // 0,0,0,0,0,0,0,0,保留字段
};
【Nginx】开发一个HTTP过滤模块的更多相关文章
- vue_cli下开发一个简单的模块权限系统之建立登录页面并且实现在浏览器输入地址出现内容
新建一个Login.vue(登录页面,先把Hello.vue的内容复制过来即可) 然后我们打开router下面的index.js,第一个箭头:(引入vue路由)第二个箭头(引入我们新建的Login.v ...
- vue_cli下开发一个简单的模块权限系统之展现数据
这个页面是用户列表:userList就是第二张截图中的data里面的userList vue中只要改变存放数据的载体就会实现页面改变,mounted的意思是页面加载时执行这里面的函数,我们需要在页面加 ...
- vue_cli下开发一个简单的模块权限系统之实现登录
因为我们需要和后端数据交互,所以我们需要安装axios,安装好以后在main.js引入 v-model是标识空间,v-on:click="doLogin"是登录事件 doLogin ...
- Nginx模块开发(5)————开发简单的HTTP过滤模块
该模块可实现如下的功能,在浏览器输入http://你的IP/lcw.text,能够读出你在根目录下创建的lcw.txt里面的内容,并在前面加上一句字符串where there is a will,th ...
- 《深入理解Nginx》阅读与实践(四):简单的HTTP过滤模块
一.Nginx的HTTP过滤模块特征 一个请求可以被任意个HTTP模块处理: 在普通HTTP模块处理请求完毕并调用ngx_http_send_header()发送HTTP头部或调用ngx_http_o ...
- iOS开发一个用户登录注册模块需要解决的坑
最近和另外一位同事负责公司登录和用户中心模块的开发工作,开发周期计划两周,减去和产品和接口的协调时间,再减去由于原型图和接口的问题,导致强迫症纠结症状高发,情绪不稳定耗费的时间,能在两周基本完成也算是 ...
- 简单的HTTP过滤模块
简单的HTTP过滤模块 一.Nginx的HTTP过滤模块特征 一个请求可以被任意个HTTP模块处理: 在普通HTTP模块处理请求完毕并调用ngx_http_send_header()发送HTTP头部或 ...
- nginx开发_配置项
nginx开发笔记_配置项 模块的配置项即nginx.conf中的指令,HTTP模块指令可以分为三个级别: main级,直接写在http{}块中的指令 server级,写在server{}块中的指令 ...
- 【Nginx】开发一个简单的HTTP模块
首先来分析一下HTTP模块是怎样介入Nginx的. 当master进程fork出若干个workr子进程后,每一个worker子进程都会在自己的for死循环中不断调用事件模块: for ( ;; ) { ...
随机推荐
- IDEA字体颜色快速导入辅助工具设置
原创链接:https://www.cnblogs.com/ka-bu-qi-nuo/p/9181954.html 程序员开发大多数都是使用IDE进行代码开发的,这样能快速的开发出需要的项目.之前一直 ...
- 封装BackgroundWorker控件(提供源代码下载,F5即可见效果)
Demo源码 背景 经常做些小程序或者小DEMO的时候会用到异步,多线程来执行一些比较耗时的工作同时将进度及时进行反馈.我通常会使用位于[ System.ComponentModel]命名空间下的Ba ...
- Xampp 配置出现403无法访问
找到\xampp\apache\conf\httpd.conf配置文件 Access forbidden! You don’t have permission to access the reques ...
- 如何解决border的重叠问题
我现在在做一个ul列表,然后给每个li加上边框,但是加完了之后,相邻列表的边框就会变成2px,比如第一个li的下边框和第二个li的上边框就会重叠在一起,请问这有什么办法解决一下么? 解决方法是: 试试 ...
- 为Anaconda添加新的源
为Anaconda添加新的源 在cmd中输入以下内容即可, 清华的源,速度非常快 conda config --add channels https://mirrors.tuna.tsinghua.e ...
- C++ POST方式访问网页
bool PostContent(CString strUrl, const CString &strPara, CString &strContent, CString &s ...
- 【java基础 16】抽象类和接口的区别
导读:前两天闲着没事儿,看了本书,然后写了点代码,在接口里面写了默认方法实现,因为书上说这个特性是从java8开始的,我还特地给测了一下java7. 没过几天,就有一个技术分享会,刚好也是讲java8 ...
- linux 命令 笔记
ftp添加用户步骤: 创建目录 sudo mkdir /home/www 为目录添加用户 sudo useradd -d /home/uftp -s /bin/bash uftp 添加用户权限 sud ...
- NOIP2012开车旅行 【倍增】
题目 小 A 和小 B 决定利用假期外出旅行,他们将想去的城市从 1 到 N 编号,且编号较小的城市在编号较大的城市的西边,已知各个城市的海拔高度互不相同,记城市 i 的海拔高度为Hi,城市 i 和城 ...
- mysql explain字段意思解释
mysql explain字段意思解释 explain包含id.select_type.table.type.possible_keys.key.key_len.ref.rows.extra字段 id ...