您的位置:首页 > 理论基础 > 计算机网络

Nginx学习笔记(八):HTTP过滤模块

2014-01-19 12:08 183 查看

前言

看了几天Nginx的访问第三方服务,大概的内容知道了,包括使用upstream以及subrequest,实质就是Nginx作为反向代理服务器。按照书中方法把代码实现了一遍,但是不知道根本,不知道怎么运行结果,原因就是对代码不熟,暂时决定放弃这一章的梳理与总结,直接跳到简单过滤模块的开发这一章学习,之后再返回来看。

HTTP过滤模块

为什么要使用过滤模块

HTTP过滤模块本质上也是一种HTTP模块,因此整个开发过程跟之前的《Hello Nginx!》是相似的。不同的是,过滤模块所做的工作只是对发送给用户的HTTP响应包做一些加工。
HTTP框架为HTTP请求的处理过程定义了11个阶段,相关代码:

typedef enum {
    NGX_HTTP_POST_READ_PHASE = 0,

    NGX_HTTP_SERVER_REWRITE_PHASE,

    NGX_HTTP_FIND_CONFIG_PHASE,
    NGX_HTTP_REWRITE_PHASE,
    NGX_HTTP_POST_REWRITE_PHASE,

    NGX_HTTP_PREACCESS_PHASE,

    NGX_HTTP_ACCESS_PHASE,
    NGX_HTTP_POST_ACCESS_PHASE,

    NGX_HTTP_TRY_FILES_PHASE,
    NGX_HTTP_CONTENT_PHASE,          // 大部分HTTP模块只在此阶段处理

    NGX_HTTP_LOG_PHASE
} ngx_http_phases;

一般而言,普通HTTP模块倾向于完成请求的核心功能(如static模块负责静态文件处理),而HTTP过滤模块则负责处理一些附加功能(如gzip压缩)。
过滤模块特性:

过滤模块效果可以根据需要叠加;
普通HTTP模块处理请求完毕,开始发送HTTP头或包体时,才开始依次调用过滤模块处理请求,因此,HTTP过滤模块只处理服务器发往客户端的响应,不处理客户端发往服务器的请求;

过滤模块顺序

在编译Nginx源码时,我们知道在ngx_modules.c文件中已经定义了一个模块数组,这个数组其实也就暗含了过滤模块的调用顺序。

过滤链表

所有HTTP过滤模块会构成一个单链表,链表的每一个元素都是一个独立的.c源文件。这个.c源文件会通过两个static静态指针(分别用于HTTP头和包体)指向下一个.c源文件。在ngx_http.c中定义两个指针,指向整个链表的首元素,也即第一个处理HTTP头和包体的方法。

typedef ngx_int_t (*ngx_http_output_header_filter_pt)(ngx_http_request_t *r);
typedef ngx_int_t (*ngx_http_output_body_filter_pt)
    (ngx_http_request_t *r, ngx_chain_t *chain);
    
extern ngx_http_output_header_filter_pt  ngx_http_top_header_filter;
extern ngx_http_output_body_filter_pt    ngx_http_top_body_filter;

然后,在每一个过滤模块做初始化工作时,会先找到链表当前的首元素,然后再使用本身的static静态类型指针:
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_XXX_filter_init(ngx_conf_t *cf)
{
    ngx_http_next_header_filter = ngx_http_top_header_filter;
    ngx_http_top_header_filter = ngx_http_XXX_header_filter;
	
    ngx_http_next_body_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_XXX_body_filter;
    
    return NGX_OK;
}

当执行ngx_http_send_header发送HTTP头时,就开始遍历所有HTTP头部过滤模块了。
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;
    }

    return ngx_http_top_header_filter(r);
}

HTTP包体链表类似。

过滤链表顺序

还是在编译Nginx源码时,编译进的ngx_modules.c模块数组,这个数组其实也就暗含了过滤模块的调用顺序。

首先,这个数组保存了所有的Nginx模块,包括HTTP普通模块与过滤模块,因此在初始化模块的顺序就按照这个该数组的成员顺序。又因为每个过滤模块都会将自己的初始化方法插入链表首部,因此过滤模块的调用顺序应该是数组中所有过滤模块的反向。具体的,结合书中内容,如下图:



过滤模块开发步骤

步骤基本与普通HTTP模块开发一致,概括总结如下:

增加.c源码文件;(HTTP过滤模块功能单一,一般一个.c文件即实现一个HTTP过滤模块)
在源码文件所在目录下创建config脚本文件,以便执行configure命令时将模块编译进Nginx;(与普通模块的config文件唯一不同就是HTTP_MODULES替换成HTTP_FILTER_MODULES)
定义过滤模块,即实例化ngx_module_t类型结构;
处理感兴趣的配置项,即设置ngx_command_t类型结构;
实现初始化方法,即插入单链表首部;
实现处理HTTP头部的方法;
实现处理HTTP包体的方法;
编译安装,修改nginx.conf文件并启动自定义过滤模块。

实例

场景:用户请求由static静态文件模块进行处理,根据URI返回磁盘文件给用户。我们开发的过滤模块会在返回给用户的响应包体前加一段字符串:“[my filter prefix]”

关于实例的执行流程,具体包括头部处理与包体处理,具体如下:



整个ngx_http_mytest_filter_module.c文件如下:
/*
* Copyright (C) Igor Sysoev
* Copyright (C) Nginx, Inc.
*/

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

typedef struct {
ngx_flag_t enable;
} ngx_http_mytest_filter_conf_t;

typedef struct {
ngx_int_t add_prefix;
} ngx_http_mytest_filter_ctx_t;

static ngx_int_t
ngx_http_mytest_fliter_init(ngx_conf_t *cf);
static void *
ngx_http_mytest_filter_create_conf(ngx_conf_t *cf);
static void *
ngx_http_mytest_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child);

static ngx_command_t ngx_http_mytest_filter_commands[] = {

{ ngx_string("add_prefix"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|
NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS,
ngx_conf_set_flag_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_mytest_filter_conf_t, enable),
NULL},

ngx_null_command
};

static ngx_http_module_t ngx_http_mytest_filter_module_ctx = {
NULL, /* preconfiguration */
ngx_http_mytest_fliter_init, /* postconfiguration */

NULL, /* create main configuration */
NULL, /* init main configuration */

NULL, /* create server configuration */
NULL, /* merge server configuration */

ngx_http_mytest_filter_create_conf, /* create location configuration */
ngx_http_mytest_filter_merge_conf /* merge location configuration */
};

ngx_module_t ngx_http_mytest_module = {
NGX_MODULE_V1,
&ngx_http_mytest_filter_module_ctx, /* module context */
ngx_http_mytest_filter_commands, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
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_str_t filter_prefix = ngx_string("[my filter prefix]");

static ngx_int_t
ngx_http_mytest_header_filter(ngx_http_request_t *r)
{
ngx_http_mytest_filter_ctx_t *ctx;
ngx_http_mytest_filter_conf_t *conf;

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_mytest_filter_module);
if (ctx) {
/* 如果已存在,说明已被调用 */
return ngx_http_next_header_filter(r);
}

conf = ngx_http_get_module_loc_conf(r, ngx_http_mytest_filter_module);

if (conf->enable == 0) {
return ngx_http_next_header_filter(r);
}

ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_mytest_filter_ctx_t));
if (ctx == NULL) {
return NGX_ERROR;
}

ctx->add_prefix = 0;

ngx_http_set_ctx(r, ctx, ngx_http_mytest_filter_module);

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;

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_mytest_body_filter(ngx_http_request_t *r)
{
ngx_http_mytest_filter_ctx_t *ctx;

ctx = ngx_http_get_module_ctx(r, ngx_http_mytest_filter_module);
if (ctx == NULL || ctx->add_prefix != 1) {
return ngx_http_next_body_filter(r);
}

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 = NULL;

return ngx_http_next_body_filter(r, cl);
}

static ngx_int_t
ngx_http_mytest_fliter_init(ngx_conf_t *cf)
{
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_mytest_header_filter;

ngx_http_next_header_filter = ngx_http_top_body_filter;
ngx_http_top_header_filter = ngx_http_mytest_body_filter;

return NGX_OK;
}

static void *
ngx_http_mytest_filter_create_conf(ngx_conf_t *cf)
{
ngx_http_mytest_filter_conf_t *mycf;

/* 创建配置结构体 */
mycf = (ngx_http_mytest_filter_conf_t *)ngx_pcalloc(cf->pool,
sizeof(ngx_http_mytest_filter_conf_t));
if (mycf == NULL) {
return NULL;
}

mycf->enable = NGX_CONF_UNSET;

return mycf;
}

static void *
ngx_http_mytest_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_mytest_filter_conf_t *prev = (ngx_http_mytest_filter_conf_t *)parent;
ngx_http_mytest_filter_conf_t *conf = (ngx_http_mytest_filter_conf_t *)child;

ngx_conf_merge_value(conf->enable, prev->enable, 0);

return NGX_OK;
}


总结

基本上是对HTTP模块开发流程的复习,另外怎么查看执行这个程序执行后的结果啊。好吧,这个我再查查。。下章开始Nginx高级数据结构的学习。再之后就开始第三部分的源码深入了。这才是重点,前面都只是铺垫。。。

主要参考

《深入理解Nginx》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: