AIGC标识 nginx-源码带读-06-过滤链与模块开发

NGINX 源码带读 第6篇:过滤链与模块开发

本篇目标

理解NGINX的输出过滤链机制,并学习如何开发自定义HTTP模块。

前置知识

  • 阅读过第1-5篇
  • 了解HTTP响应格式

1. 过滤链(Filter Chain)

1.1 概念

NGINX使用过滤链(filter chain)处理HTTP响应。每个过滤器负责响应的一个方面,多个过滤器串联形成处理流水线。

1.2 过滤器类型

类型 说明
Header Filter 处理响应头(Content-Type、Content-Length等)
Body Filter 处理响应体(gzip压缩、chunk编码等)

1.3 核心过滤器

Header filter链(按顺序执行):
  ngx_http_not_modified_filter      修改304响应
  ngx_http_headers_filter          设置响应头
  ngx_http_image_filter            图片处理(如有)
  ngx_http_gzip_header_filter      gzip头处理
  ngx_http_write_filter            写入socket(链表末端)

Body filter链(按顺序执行):
  ngx_http_gzip_body_filter        gzip压缩
  ngx_http_chunked_filter          chunk编码
  ngx_http_write_filter            写入socket(链表末端)

1.4 Header Filter链

// src/http/ngx_http_header_filter_module.c
ngx_http_output_header_filter_pt ngx_http_next_header_filter;

ngx_int_t ngx_http_header_filter(ngx_http_request_t *r) {
    // 1. 检查是否需要发送响应头
    if (r->header_only) {
        return ngx_http_next_header_filter(r);
    }

    // 2. 构建响应头
    // 状态行:HTTP/1.1 200 OK
    // 头部字段:Content-Type, Content-Length等

    // 3. 调用下一个过滤器
    return ngx_http_next_header_filter(r);
}

1.5 Body Filter链

// src/http/ngx_http_write_filter_module.c
ngx_http_output_body_filter_pt ngx_http_next_body_filter;

ngx_int_t ngx_http_body_filter(ngx_http_request_t *r, ngx_chain_t *in) {
    ngx_chain_t *cl;

    // 1. 遍历chain链表
    for (cl = in; cl; cl = cl->next) {
        // 2. 检查缓冲区
        if (cl->buf->pos == cl->buf->last) {
            continue;  // 空缓冲区
        }

        // 3. 检查是否是最后一个缓冲区
        if (cl->buf->last_buf) {
            r->connection->buffered &= ~NGX_HTTP_LOWEL_BUFFERED;
        }
    }

    // 4. 调用下一个过滤器
    return ngx_http_next_body_filter(r, in);
}

1.6 ngx_http_write_filter —— 最末端过滤器

这是过滤链的末端,负责将响应数据写入socket:

// src/http/ngx_http_write_filter_module.c
ngx_int_t ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in) {
    ngx_connection_t *c;
    ngx_event_t *wev;
    ngx_chain_t *cl, *ln;
    ngx_buf_t *b;
    ssize_t n;
    ngx_int_t rc;

    c = r->connection;
    wev = c->write;

    // 1. 将chain追加到输出链表
    if (in == NULL) {
        return NGX_OK;
    }

    // 2. 遍历chain,计算总大小
    size = 0;
    for (cl = in; cl; cl = cl->next) {
        b = cl->buf;
        size += ngx_buf_size(b);
    }

    // 3. 如果有数据需要发送
    if (size > 0) {
        // 尝试直接发送
        n = ngx_write_chain(c, in);

        if (n == NGX_AGAIN) {
            // 需要等待写事件
            wev->handler = ngx_http_write_filter_handler;
            ngx_add_timer(wev, r->send_timeout);
            return NGX_AGAIN;
        }

        if (n == NGX_ERROR) {
            ngx_http_finalize_request(r, NGX_HTTP_SERVER_ERROR);
            return NGX_ERROR;
        }

        // 4. 检查是否发送完成
        if (cl->buf->last_buf) {
            r->connection->buffered &= ~NGX_HTTP_LOWEL_BUFFERED;
        }
    }

    return NGX_OK;
}

1.7 过滤链初始化

// src/http/ngx_http.c
static ngx_int_t ngx_http_init_phases(ngx_conf_t *cf) {
    // 初始化header filter链
    ngx_http_next_header_filter = ngx_http_header_filter;

    // 初始化body filter链
    ngx_http_next_body_filter = ngx_http_body_filter;

    return NGX_OK;
}

2. 缓冲区和链表

2.1 ngx_buf_t

// src/core/ngx_buf.h
typedef struct ngx_buf_s {
    u_char *pos;           // 内存缓冲区起始位置
    u_char *last;          // 内存缓冲区结束位置
    off_t file_pos;        // 文件缓冲区起始偏移
    off_t file_last;       // 文件缓冲区结束偏移

    ngx_buf_tag_t tag;     // 缓冲区标记(用于过滤器识别)
    ngx_output_chain_t *output_chain; // 输出链
    ngx_buf_file_t file;   // 文件对象

    unsigned temporary:1;  // 内存缓冲区(可修改)
    unsigned memory:1;     // 内存缓冲区(只读)
    unsigned mmap:1;       // mmap映射的内存
    unsigned in_file:1;    // 文件缓冲区
    unsigned flush:1;      // 刷新标记
    unsigned sync:1;       // 同步标记
    unsigned last_buf:1;   // 最后一个缓冲区(响应结束)
    unsigned last_in_chain:1; // 链中最后一个
    unsigned temp_file:1;  // 临时文件
} ngx_buf_t;

2.2 缓冲区标志位说明

标志位 含义
temporary 内存缓冲区,可修改,发送后可释放
memory 内存缓冲区,只读,发送后不可释放
mmap mmap映射的内存,需要munmap
in_file 文件缓冲区,通过sendfile发送
flush 刷新标记,强制发送
sync 同步标记,等待发送完成
last_buf 响应的最后一个缓冲区
last_in_chain 当前chain的最后一个缓冲区

2.3 ngx_chain_t

// src/core/ngx_buf.h
struct ngx_chain_s {
    ngx_buf_t *buf;     // 缓冲区
    ngx_chain_t *next;  // 下一个链节点
};

链表是NGINX在各模块间传递数据的主要方式。

2.4 缓冲区创建

// 创建临时缓冲区
ngx_buf_t *ngx_create_temp_buf(ngx_pool_t *pool, size_t size) {
    ngx_buf_t *b;

    b = ngx_calloc_buf(pool);
    if (b == NULL) {
        return NULL;
    }

    b->pos = ngx_palloc(pool, size);
    if (b->pos == NULL) {
        return NULL;
    }

    b->last = b->pos;
    b->temporary = 1;  // 临时内存缓冲区

    return b;
}

// 创建文件缓冲区
ngx_buf_t *ngx_create_file_buf(ngx_pool_t *pool, ngx_fd_t fd,
    off_t offset, size_t size) {
    ngx_buf_t *b;

    b = ngx_calloc_buf(pool);
    if (b == NULL) {
        return NULL;
    }

    b->in_file = 1;
    b->file = ngx_pcalloc(pool, sizeof(ngx_file_t));
    b->file->fd = fd;
    b->file_pos = offset;
    b->file_last = offset + size;

    return b;
}

3. 自定义HTTP模块实战

3.1 模块结构

一个HTTP模块需要:

  1. 模块定义(ngx_module_t)
  2. 模块上下文(ngx_http_module_t)
  3. 配置结构体(存储模块配置)
  4. 配置命令(ngx_command_t)
  5. 处理函数(实际逻辑)

3.2 示例:返回当前时间的模块

// ngx_http_mytime_module.c

// 1. 配置结构体
typedef struct {
    ngx_flag_t enable;
} ngx_http_mytime_loc_conf_t;

// 2. 内容处理函数
static ngx_int_t ngx_http_mytime_handler(ngx_http_request_t *r) {
    // 设置响应头
    ngx_str_set(&r->headers_out.content_type, "text/plain");
    r->headers_out.status = NGX_HTTP_OK;

    // 构建响应体
    u_char *body = ngx_pnalloc(r->pool, 64);
    ngx_sprintf(body, "Current time: %T\n", ngx_time());

    // 发送响应
    r->headers_out.content_length_n = ngx_strlen(body);

    ngx_buf_t *b = ngx_create_temp_buf(r->pool, ngx_strlen(body));
    b->pos = body;
    b->last = body + ngx_strlen(body);
    b->last_in_chain = 1;
    b->last_buf = 1;

    ngx_chain_t out;
    out.buf = b;
    out.next = NULL;

    return ngx_http_output_filter(r, &out);
}

// 3. 配置命令
static ngx_command_t ngx_http_mytime_commands[] = {
    { ngx_string("mytime"),
      NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_mytime_loc_conf_t, enable),
      NULL },
    ngx_null_command
};

// 4. 模块上下文
static ngx_http_module_t ngx_http_mytime_module_ctx = {
    NULL,  // preconfiguration
    NULL,  // postconfiguration
    NULL,  // create_main_conf
    NULL,  // init_main_conf
    NULL,  // create_srv_conf
    NULL,  // merge_srv_conf
    ngx_http_mytime_create_loc_conf,
    ngx_http_mytime_merge_loc_conf
};

// 5. 模块定义
ngx_module_t ngx_http_mytime_module = {
    NGX_MODULE_V1,
    &ngx_http_mytime_module_ctx,
    ngx_http_mytime_commands,
    NGX_HTTP_MODULE,
    NULL, NULL, NULL, NULL, NULL,
    NULL, NULL,
    NULL,
    NGX_MODULE_V1_PADDING
};

// 6. 创建loc_conf
static void *ngx_http_mytime_create_loc_conf(ngx_conf_t *cf) {
    ngx_http_mytime_loc_conf_t *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_mytime_loc_conf_t));
    if (conf == NULL) {
        return NULL;
    }

    conf->enable = NGX_CONF_UNSET;

    return conf;
}

// 7. 合并loc_conf
static char *ngx_http_mytime_merge_loc_conf(ngx_conf_t *cf,
    void *parent, void *child) {
    ngx_http_mytime_loc_conf_t *prev = parent;
    ngx_http_mytime_loc_conf_t *conf = child;

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

    // 如果启用,注册handler
    if (conf->enable) {
        ngx_http_handler_pt *h;
        ngx_http_core_loc_conf_t *clcf;

        clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
        h = ngx_array_push(&clcf->handlers);
        if (h == NULL) {
            return NGX_CONF_ERROR;
        }

        *h = ngx_http_mytime_handler;
    }

    return NGX_CONF_OK;
}

3.3 编译方式

# 静态编译
# 在objs/ngx_modules.c中添加模块
./configure --add-module=/path/to/module
make

# 动态编译
./configure --add-dynamic-module=/path/to/module
make modules
# 生成 objs/ngx_http_mymodule.so

3.4 使用

location /time {
    mytime on;
}

4. 过滤链定制

4.1 在过滤链中插入自定义过滤器

// 在postconfiguration中:
static ngx_int_t ngx_http_my_filter_init(ngx_conf_t *cf) {
    ngx_http_next_body_filter = ngx_http_body_filter;
    ngx_http_body_filter = ngx_http_my_body_filter;

    return NGX_OK;
}

这是经典的责任链模式——每个过滤器处理自己负责的部分,然后调用下一个。

4.2 过滤器执行顺序

请求到达
  -> handler生成响应
    -> ngx_http_output_filter(r, &out)
      -> header_filter_1 -> header_filter_2 -> ... -> write
      -> body_filter_1 -> body_filter_2 -> ... -> write

4.3 过滤器短路

某些过滤器可以短路处理:

ngx_int_t ngx_http_not_modified_filter(ngx_http_request_t *r) {
    // 检查是否需要304响应
    if (r->headers_in.if_modified_since != NULL) {
        if (r->headers_out.last_modified_time <= if_modified_since) {
            r->headers_out.status = NGX_HTTP_NOT_MODIFIED;
            r->headers_out.content_length_n = 0;
            r->header_only = 1;

            // 跳过body过滤器
            return ngx_http_next_header_filter(r);
        }
    }

    return ngx_http_next_header_filter(r);
}

5. 静态文件模块

5.1 文件发送流程

proxy_pass / static文件
  -> ngx_http_static_module.c
    -> ngx_http_static_handler()
      -> open(file_path)
      -> stat(file_path)
      -> 创建in_file buf
      -> ngx_http_output_filter(r, &out)
        -> write_filter通过sendfile发送

5.2 sendfile优化

// src/os/unix/ngx_files.c
ssize_t ngx_write_chain(ngx_connection_t *c, ngx_chain_t *in) {
    // 尝试使用sendfile
    if (c->sendfile) {
        n = ngx_write_chain_to_file(c, in, c->file, c->offset);
    } else {
        // 回退到writev
        n = ngx_write_chain_to_socket(c, in);
    }

    return n;
}

6. 本篇小结

概念 要点
过滤链 header filter + body filter,责任链模式
缓冲区 ngx_buf_t,支持内存和文件
链表 ngx_chain_t,模块间数据传递
模块开发 定义+上下文+配置+命令+处理函数
过滤器插入 postconfiguration中链表替换
write_filter 最末端过滤器,实际发送数据

思考题

  1. 为什么NGINX用过滤链而不是直接在处理函数中构建响应?
  2. gzip过滤器应该插入过滤链的哪个位置?为什么?
  3. 如果要实现一个HTTP缓存模块,需要在哪几个阶段工作?

思考题解答

1. 为什么用过滤链而不是直接构建响应?

过滤链的优势

  1. 单一职责原则:每个过滤器只负责一个功能(如gzip压缩、chunk编码、添加响应头),代码更清晰、更易维护。

  2. 可组合性:可以通过配置灵活组合过滤器,无需修改核心代码。例如:

    gzip on;           # 启用gzip过滤器
    add_header X-Custom "value";  # 添加自定义头
    
  3. 可扩展性:新功能可以通过添加过滤器实现,不影响现有过滤器。例如:添加一个记录响应时间的过滤器。

  4. 性能优化:过滤器可以短路处理(如304响应不需要body过滤器),避免不必要的计算。

  5. 复用性:过滤器可以跨多个location复用,无需重复实现。

对比直接构建响应

// 直接构建(不推荐)
ngx_int_t handler(ngx_http_request_t *r) {
    // 1. 检查是否需要gzip
    // 2. 检查是否需要chunk编码
    // 3. 检查是否需要添加自定义头
    // 4. 检查是否需要304处理
    // 5. 构建响应
    // 6. 发送响应
    // 所有逻辑混在一起,难以维护
}

过滤链模式

// 过滤链处理
handler -> header_filter -> chunk_filter -> gzip_filter -> write_filter
// 每个过滤器只关注自己的职责

2. gzip过滤器应该插入过滤链的哪个位置?

最佳位置:在header filter链的末尾之前,body filter链的中间

原因

  1. header filter:gzip需要修改响应头(添加 Content-Encoding: gzip),所以在header filter链中工作。

  2. body filter:gzip需要压缩响应体,所以在body filter链中处理。

  3. 位置选择

    • ngx_http_not_modified_filter 之后(避免压缩304响应)
    • ngx_http_chunked_filter 之前(压缩后编码)
    • ngx_http_headers_filter 之后(压缩前添加必要的头)

过滤链顺序

Header filter链:
  realip -> not_modified -> headers -> gzip_header -> ... -> write

Body filter链:
  ... -> gzip_body -> chunked -> write

配置影响

gzip on;
gzip_types text/plain application/json;
gzip_min_length 1024;  # 小于1024字节不压缩

gzip过滤器会在body filter链中检查这些配置,决定是否压缩。

3. 实现HTTP缓存模块需要在哪几个阶段工作?

需要工作的阶段

  1. PREACCESS阶段(阶段5)

    • 检查缓存是否存在
    • 如果缓存命中,直接返回缓存内容
    • 实现:ngx_http_cache_filter 模块
  2. CONTENT阶段(阶段9)

    • 如果缓存未命中,代理到后端
    • 将后端响应存入缓存
    • 实现:ngx_http_proxy_module 中的缓存逻辑
  3. LOG阶段(阶段10)

    • 记录缓存命中/未命中状态
    • 用于监控和统计

缓存处理流程

请求到达
  -> PREACCESS:检查缓存
    -> 缓存命中:直接返回缓存内容
    -> 缓存未命中:继续
  -> ACCESS:权限检查
  -> CONTENT:代理到后端
    -> 收到响应
    -> 存入缓存
    -> 返回响应

缓存模块的关键组件

typedef struct {
    ngx_flag_t enable;           // 是否启用缓存
    ngx_str_t cache_path;        // 缓存路径
    ngx_uint_t cache_size;       // 缓存大小
    ngx_uint_t cache_ttl;        // 缓存过期时间
    ngx_http_cache_key_t key;    // 缓存键
} ngx_http_cache_loc_conf_t;

缓存键的设计

  • 默认使用 $scheme$proxy_host$request_uri
  • 可以自定义:proxy_cache_key $scheme$host$request_uri
  • 支持多个key的hash

缓存策略

  • proxy_cache_valid 200 302 10m;:200/302响应缓存10分钟
  • proxy_cache_valid 404 1m;:404响应缓存1分钟
  • proxy_cache_use_stale error timeout updating;:在错误时使用过期缓存
posted @ 2026-09-04 17:26  IcarusLee  阅读(1)  评论(0)    收藏  举报