1. 引言

Nginx 以其高性能和模块化设计闻名。对于希望扩展 Nginx 功能的开发者来说,编写自定义模块是必经之路。在 Nginx HTTP 模块开发中,ngx_http_request_t 结构体是贯穿整个请求生命周期的核心数据结构。它承载了客户端请求的所有信息,从原始的 HTTP 请求行、请求头,到处理过程中的上下文、输出过滤链,无所不包。

本文将深入剖析 ngx_http_request_t 结构体中的关键字段,并通过一个完整的、可直接运行的 Nginx 模块示例,演示如何在实际开发中使用这些字段。我们将采用标准的 config 文件方式来编译和加载模块,确保你能够快速上手。

2. ngx_http_request_t 结构体概览

ngx_http_request_t 定义在 Nginx 源码的 src/http/ngx_http_request.h 文件中。它是一个非常庞大的结构体,包含了数百个字段。不过,对于初学者来说,掌握其中最关键、最常用的字段就足以应对大部分开发场景。

我们可以将这些字段大致分为几类:

  • 请求基本信息:如请求方法、URI、协议版本等。
  • 请求头部:原始请求头、解析后的请求头。
  • 请求体:请求体的读取状态和内容。
  • 连接与套接字:底层的连接对象。
  • 模块上下文:用于在模块内部各处理阶段间传递数据的 ctx
  • 输出相关:响应头和响应体。
  • 资源管理:内存池 pool,用于管理请求生命周期内的内存分配。

3. 核心字段详解

下面我们逐一介绍 ngx_http_request_t 中最常用的一些字段。

3.1 请求基本信息

  • args (ngx_str_t):请求的查询参数部分。例如,请求 /api/user?name=test&age=18argsname=test&age=18

    args 的典型用法:在模块中,你可以直接通过 r->args 获取原始查询字符串。如果需要解析单个参数,Nginx 提供了 ngx_http_arg(r, key, key_len,ngx_str_t *value) 宏,可以按名称查找参数值。例如:

    ngx_str_t  name;
    if (ngx_http_arg(r, (u_char *) "name", 4,&name) == NGX_OK) {
        // name.data 指向参数值,name.len 为参数值长度
    }
    

    如果需要遍历所有参数,可以手动解析 r->args.data,按 & 分割键值对,再按 = 分割键和值。注意 args 是原始字符串,Nginx 不会自动进行 URL 解码,若参数包含 %XX 编码,需自行调用 ngx_unescape_uri 解码。

    在实战模块中,我们可以利用 args 实现更灵活的功能,例如根据查询参数返回不同的响应内容。下面是一个在已有模块基础上扩展的示例片段:

    // 在 ngx_http_hello_handler 中,解析 ?name=xxx 参数
    ngx_str_t  name;
    if (ngx_http_arg(r, (u_char *) "name", 4,&name) == NGX_OK) {
        // 使用 name 参数值构建响应
        response.len = ngx_snprintf(response.data, NGX_MAX_ERROR_STR,
                                    "{\"user_agent\": \"%V\", \"name\": \"%V\"}",
                                    &ctx->custom_header_value, &name)
                       - response.data;
    }
    

    通过 curl "http://localhost/hello?name=World" 即可测试带查询参数的请求。

  • method_name (ngx_str_t):请求方法的字符串表示,例如 "GET""POST"

  • method (ngx_uint_t):请求方法的枚举值,例如 NGX_HTTP_GETNGX_HTTP_POST。通常用这个字段做判断,效率更高。

  • uri (ngx_str_t):请求的 URI,不包含查询参数。例如,请求 /api/user?name=testuri/api/user

  • http_protocol (ngx_str_t):HTTP 协议版本,例如 "HTTP/1.1""HTTP/2.0"

  • http_version (ngx_uint_t):HTTP 协议版本的枚举值,例如 NGX_HTTP_VERSION_11NGX_HTTP_VERSION_20

3.2 请求头部

  • headers_in (ngx_http_headers_in_t):这是一个结构体,包含了所有解析后的请求头。例如:
    • headers_in->hostHost 头。
    • headers_in->content_typeContent-Type 头。
    • headers_in->content_length_nContent-Length 头的数值。
    • headers_in->user_agentUser-Agent 头。
    • headers_in->cookiesCookie 头(以链表形式存储)。
  • headers_out (ngx_http_headers_out_t):用于设置响应头的结构体。例如:
    • headers_out->status:设置 HTTP 状态码,如 NGX_HTTP_OK (200)。
    • headers_out->content_type:设置 Content-Type 响应头。
    • headers_out->content_length_n:设置 Content-Length 响应头。

3.3 请求体

  • request_body (ngx_http_request_body_t *):指向请求体结构的指针。当需要读取 POSTPUT 请求的 body 时使用。
    • request_body->buf:指向存储请求体数据的缓冲区。
    • request_body->temp_file:如果请求体太大,Nginx 会将其暂存到临时文件中,此字段指向该文件。

3.4 连接与上下文

  • connection (ngx_connection_t *):指向底层 TCP 连接的指针。通过它可以获取客户端 IP、端口等信息。
    • connection->addr_text:客户端的 IP 地址和端口。
  • ctx (void *):这是一个非常重要的字段,用于在模块内部的不同处理阶段(如 initbody handlerfinalize)之间共享数据。通常,我们会定义一个模块专用的上下文结构体,然后通过 ngx_http_get_module_ctx(r, module)ngx_http_set_ctx(r, ctx, module) 来存取。
SSL/TLS 相关字段

当 Nginx 配置了 HTTPS(SSL/TLS)时,ngx_http_request_t 通过 connection 指针可以访问到底层 SSL 连接的相关信息。这些字段对于需要处理加密连接、获取客户端证书或检查 SSL 握手状态的模块至关重要。

  • connection->ssl (ngx_ssl_connection_t *):指向 SSL 连接结构体的指针。如果当前请求是通过 HTTPS 建立的,此字段非 NULL。通过它可以访问 SSL 会话的详细信息。
  • connection->ssl->connection (SSL *):OpenSSL 的 SSL 对象指针。通过它可以调用 OpenSSL API 获取更底层的 TLS 信息,例如:
    • SSL_get_cipher_name(ssl->connection):获取当前使用的加密套件名称。
    • SSL_get_version(ssl->connection):获取 TLS 协议版本(如 "TLSv1.3")。
    • SSL_get_verify_result(ssl->connection):获取客户端证书验证结果。
  • connection->ssl->handshaked (ngx_flag_t):标记 SSL 握手是否已完成。在 NGX_HTTP_SSL_HANDSHAKE_PHASE 阶段之后,此字段为 1。模块在处理请求时通常可以假定握手已完成,但了解此字段有助于调试连接层面的问题。
  • connection->ssl->session_ctx (ngx_ssl_session_t *):指向 SSL 会话缓存上下文。可用于会话复用(session resumption)相关的操作,提升后续 HTTPS 请求的性能。

实战示例:在 handler 中获取 SSL 信息

static ngx_int_t
ngx_http_hello_handler(ngx_http_request_t *r)
{
    // 检查是否为 HTTPS 连接
    if (r->connection->ssl) {
        ngx_ssl_connection_t *ssl = r->connection->ssl;
        SSL *ssl_obj = ssl->connection;

        // 获取 TLS 版本和加密套件
        const char *tls_version = SSL_get_version(ssl_obj);
        const char *cipher_name = SSL_get_cipher_name(ssl_obj);

        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                      "HTTPS request: TLS=%s, Cipher=%s",
                      tls_version, cipher_name);
    } else {
        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                      "HTTP request (non-SSL)");
    }

    // 继续处理请求...
}

注意事项

  1. 编译依赖:使用 OpenSSL API 需要在模块源码中包含 <openssl/ssl.h> 头文件,并在编译时链接 OpenSSL 库。Nginx 在 ./configure --with-http_ssl_module 时会自动处理这些依赖。
  2. 空指针检查:在访问 r->connection->ssl 之前,务必检查其是否为 NULL,因为非 HTTPS 请求下该字段为 NULL,直接访问会导致段错误。
  3. 握手阶段:在 NGX_HTTP_SSL_HANDSHAKE_PHASE 阶段之前,SSL 握手可能尚未完成,此时 r->connection->ssl->handshaked0。在 content handler 中(如本例),握手通常已完成,可以安全访问 SSL 信息。
  4. 性能影响:频繁调用 OpenSSL API(如 SSL_get_cipher_name)可能带来轻微性能开销,建议在需要时才获取,而非在每个请求中无条件调用。

3.5 输出与资源管理

  • pool (ngx_pool_t *):这是 Nginx 的内存池。请求生命周期内所有需要分配的内存,都应该通过 ngx_pcalloc(r->pool, size)ngx_palloc(r->pool, size) 来申请。Nginx 会在请求结束后自动释放整个内存池,无需手动管理。
  • main (ngx_http_request_t *):指向主请求的指针。在子请求(subrequest)中,main 指向发起它的父请求。对于主请求,main 指向自身。
  • parent (ngx_http_request_t *):指向创建当前子请求的父请求。对于主请求,此字段为 NULL
  • count (ngx_uint_t):请求的引用计数。当一个请求被多个部分(如子请求、异步事件)引用时,此计数会增加。当计数归零时,Nginx 才会真正销毁该请求。
  • phase_handler (ngx_uint_t):当前请求所处的处理阶段索引。Nginx 将 HTTP 请求处理分为多个阶段(如 NGX_HTTP_SERVER_REWRITE_PHASENGX_HTTP_CONTENT_PHASE),这个字段用于控制阶段间的跳转。

3.6 获取各级 config 的原理

Nginx 的配置体系分为三个层级:main(全局)server(虚拟主机)location(路径匹配)。每个层级都可以定义模块专属的配置结构体,模块通过 Nginx 提供的宏来获取当前请求所对应的配置。

配置结构体的定义与注册

在模块开发中,通常定义一个 ngx_http_xxx_loc_conf_t 结构体来存放 location 级别的配置项。例如:

typedef struct {
    ngx_flag_t enable;
    ngx_str_t  greeting;
} ngx_http_hello_loc_conf_t;

然后在模块的 create_loc_conf 回调中分配并初始化该结构体,在 merge_loc_conf 回调中将父层级的配置与子层级的配置合并。Nginx 在解析配置时会自动调用这些回调。

获取配置的宏

Nginx 提供了三个核心宏来获取不同层级的配置:

  • ngx_http_get_module_loc_conf(r, module):获取当前请求所在 location 的模块配置。这是最常用的宏,因为大多数模块的行为由 location 块内的指令控制。
  • ngx_http_get_module_srv_conf(r, module):获取当前请求所在 server 的模块配置。适用于需要在 server 级别统一设置的参数。
  • ngx_http_get_module_main_conf(r, module):获取全局 main 级别的模块配置。适用于跨所有 server 和 location 共享的全局参数。

这三个宏的底层实现都依赖于 r->loc_confr->srv_confr->main_conf 这三个指针数组。Nginx 在初始化时为每个模块分配了索引(module->ctx_index),宏通过该索引从对应的数组中取出配置指针:

// 简化后的实现逻辑
#define ngx_http_get_module_loc_conf(r, module) \
    ((ngx_http_hello_loc_conf_t *) ((r)->loc_conf[module.ctx_index]))
实战示例:在 handler 中获取配置

ngx_http_hello_handler 中,我们可以这样获取 location 配置并读取 greeting 指令的值:

static ngx_int_t
ngx_http_hello_handler(ngx_http_request_t *r)
{
    ngx_http_hello_loc_conf_t *hlcf;

    hlcf = ngx_http_get_module_loc_conf(r, ngx_http_hello_module);
    if (hlcf->enable == 0) {
        return NGX_DECLINED;  // 未启用则跳过
    }

    // 使用 hlcf->greeting 构建响应
    // ...
}
配置合并的优先级

Nginx 的配置合并遵循「子层级覆盖父层级」的原则:

  1. 如果 location 块中显式设置了某指令,则使用 location 级别的值。
  2. 如果 location 块未设置,则继承 server 级别的值。
  3. 如果 server 也未设置,则使用 main 级别的默认值(由 create_loc_conf 中设定的初始值决定)。

这种机制使得模块配置既灵活又简洁——用户只需在需要覆盖的地方显式指定指令即可。

3.7 异步处理的具体做法

Nginx 的高性能核心在于其事件驱动、非阻塞的异步处理模型。在模块开发中,如果 handler 中需要执行耗时操作(如数据库查询、外部 HTTP 请求、文件 I/O),绝不能阻塞当前进程,而应使用 Nginx 提供的异步机制。

异步处理的基本流程

Nginx 的异步处理通常遵循以下步骤:

  1. 增加请求引用计数:使用 r->main->count++ 防止请求在异步等待期间被意外销毁。
  2. 注册事件处理函数:通过 ngx_add_timerngx_add_event 注册一个超时或 I/O 事件。
  3. 返回 NGX_AGAIN:告诉 Nginx 当前请求尚未完成,后续会通过事件回调继续处理。
  4. 在事件回调中恢复处理:当事件触发时,Nginx 调用注册的回调函数,继续完成请求。
使用定时器实现异步延迟处理

下面是一个使用 ngx_add_timer 实现异步延迟响应的示例:

// 异步事件回调函数
static void
ngx_http_hello_async_handler(ngx_event_t *ev)
{
    ngx_http_request_t          *r;
    ngx_http_hello_ctx_t        *ctx;

    // 从事件数据中恢复请求指针
    r = ev->data;
    ctx = ngx_http_get_module_ctx(r, ngx_http_hello_module);

    // 减少引用计数,允许 Nginx 销毁请求
    r->main->count--;
    // 构建并发送响应
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_type.len = sizeof("text/plain") - 1;
    r->headers_out.content_type.data = (u_char *) "text/plain";
    ngx_http_send_header(r);

    // 发送响应体
    ngx_str_t response = ngx_string("Async response after 1 second");
    ngx_buf_t *b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    b->pos = response.data;
    b->last = response.data + response.len;
    b->memory = 1;
    b->last_buf = 1;

    ngx_chain_t out;
    out.buf = b;
    out.next = NULL;
    ngx_http_output_filter(r, &out);


}

// handler 中启动异步操作
static ngx_int_t
ngx_http_hello_handler(ngx_http_request_t *r)
{
    ngx_http_hello_ctx_t *ctx;

    // 分配上下文
    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_hello_ctx_t));
    if (ctx == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    ngx_http_set_ctx(r, ctx, ngx_http_hello_module);

    // 增加请求引用计数,防止请求在异步等待期间被销毁
    r->main->count++;

    // 设置定时器,1 秒后触发异步回调
    ngx_event_t *ev = ngx_pcalloc(r->pool, sizeof(ngx_event_t));
    if (ev == NULL) {
        r->main->count--;
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    ev->data = r;
    ev->handler = ngx_http_hello_async_handler;
    ev->log = r->connection->log;
    ngx_add_timer(ev, 1000);  // 1000 毫秒

    // 返回 NGX_AGAIN,告诉 Nginx 请求尚未完成
    return NGX_AGAIN;
}
使用子请求实现异步组合

Nginx 的**子请求(subrequest)**机制是另一种强大的异步处理方式。子请求允许一个请求内部发起多个并发的子请求,然后将它们的响应组合成最终结果:

static ngx_int_t
ngx_http_hello_handler(ngx_http_request_t *r)
{
    ngx_http_request_t *sr;
    ngx_http_post_subrequest_t *ps;

    // 准备子请求参数
    ps = ngx_pcalloc(r->pool, sizeof(ngx_http_post_subrequest_t));
    if (ps == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    ps->handler = ngx_http_hello_subrequest_done;

    // 发起子请求到 /backend/data
    ngx_str_t uri = ngx_string("/backend/data");
    ngx_str_t args = ngx_string("");
    ngx_int_t rc = ngx_http_subrequest(r, &uri, &args, &sr, ps, NGX_HTTP_SUBREQUEST_CLONE);
    if (rc != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    // 增加主请求引用计数,等待子请求完成
    r->main->count++;

    return NGX_AGAIN;
}

// 子请求完成后的回调
static ngx_int_t
ngx_http_hello_subrequest_done(ngx_http_request_t *r, void *data, ngx_int_t rc)
{
    // 子请求的响应数据已存储在 sr->headers_out 和 sr->out 中
    // 可以在此处组合最终响应

    // 减少引用计数
    r->main->count--;

    return NGX_OK;
}
异步处理的关键注意事项
  1. 引用计数管理:每次发起异步操作前必须 r->main->count++,操作完成后必须 r->main->count--。计数归零时 Nginx 会销毁请求,过早归零会导致野指针。
  2. 内存安全:异步回调中访问的 r 指针必须确保请求仍然存活。建议在回调中通过事件结构体的 data 字段传递请求指针,而不是使用全局变量。
  3. 错误处理:如果异步操作失败(如分配内存失败),必须及时减少引用计数并返回错误,避免请求泄漏。
  4. 不要在回调中阻塞:异步回调本身仍然在 Nginx 的事件循环中执行,同样不能执行阻塞操作。如果需要再次等待,可以继续注册新的事件。
异步处理流程总览

下图以定时器异步为例,展示了 Nginx 模块异步处理的完整流程与数据流向:

数据流向

请求进入 handler

分配模块上下文 ctx

r->main->count++

注册事件(ngx_add_timer)

返回 NGX_AGAIN

Nginx 事件循环等待

定时器超时触发

事件回调 ngx_http_hello_async_handler

从 ev->data 恢复 r 指针

构建响应(headers_out / output_filter)

r->main->count--

请求完成,Nginx 销毁请求

handler 分配 ctx 并存入 r->ctx

ev->data = r(传递请求指针)

回调中通过 ev->data 取回 r

流程说明

  1. 请求进入 handler:Nginx 在 content phase 调用模块的 handler 函数。
  2. 增加引用计数r->main->count++ 防止请求在异步等待期间被销毁。
  3. 注册事件:通过 ngx_add_timer 注册超时事件,并将请求指针 r 存入 ev->data
  4. 返回 NGX_AGAIN:告知 Nginx 当前请求尚未完成,后续由事件回调继续处理。
  5. 事件循环等待:Nginx 继续处理其他事件,不阻塞当前进程。
  6. 事件触发:定时器超时后,Nginx 调用注册的回调函数。
  7. 恢复请求指针:回调中通过 ev->data 取回请求指针 r
  8. 构建并发送响应:设置响应头、分配缓冲区、调用 ngx_http_output_filter 发送数据。
  9. 减少引用计数r->main->count--,允许 Nginx 在响应发送完成后销毁请求。
  10. 请求完成:引用计数归零,Nginx 回收请求资源。

4. 实战:编写一个完整的 Nginx 模块

理论讲完了,我们来动手写一个模块。这个模块的功能是:拦截所有对 /hello 路径的请求,解析请求头中的 User-Agent 和可选的 X-Custom-Header,然后返回一个包含这些信息的 JSON 响应。

4.1 模块代码 (ngx_http_hello_module.c)

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

// 模块上下文结构体,用于在模块内部传递数据
typedef struct {
    ngx_str_t custom_header_value;
} ngx_http_hello_ctx_t;

// 处理函数声明
static ngx_int_t ngx_http_hello_handler(ngx_http_request_t *r);
static ngx_int_t ngx_http_hello_init(ngx_conf_t *cf);

// 模块配置结构体(本例中未使用,但保留以展示标准结构)
typedef struct {
    ngx_flag_t enable;
} ngx_http_hello_loc_conf_t;

// 处理函数
static ngx_int_t
ngx_http_hello_handler(ngx_http_request_t *r)
{
    ngx_http_hello_ctx_t  *ctx;
    ngx_str_t              response;
    ngx_table_elt_t       *user_agent;
    ngx_table_elt_t       *custom_header;
    ngx_buf_t             *b;
    ngx_chain_t            out;

    // 1. 只处理 GET 请求
    if (r->method != NGX_HTTP_GET) {
        return NGX_HTTP_NOT_ALLOWED;
    }

    // 2. 分配模块上下文,并存储自定义头信息
    ctx = ngx_http_get_module_ctx(r, ngx_http_hello_module);
    if (ctx == NULL) {
        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_hello_ctx_t));
        if (ctx == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }
        ngx_http_set_ctx(r, ctx, ngx_http_hello_module);
    }

    // 3. 获取 User-Agent 请求头
    user_agent = r->headers_in.user_agent;
    if (user_agent == NULL) {
        // 如果没有 User-Agent,使用默认值
        ctx->custom_header_value.data = (u_char *) "Unknown";
        ctx->custom_header_value.len = ngx_strlen("Unknown");
    } else {
        ctx->custom_header_value = user_agent->value;
    }

    // 4. 获取自定义请求头 X-Custom-Header
    custom_header = r->headers_in.headers.part.elts;
    // 遍历请求头链表,查找 X-Custom-Header
    ngx_list_part_t *part = &r->headers_in.headers.part;
    ngx_table_elt_t *header = part->elts;
    ngx_uint_t i;
    for (i = 0; /* void */; i++) {
        if (i >= part->nelts) {
            if (part->next == NULL) {
                break;
            }
            part = part->next;
            header = part->elts;
            i = 0;
        }
        if (ngx_strcasecmp(header[i].key.data, (u_char *) "X-Custom-Header") == 0) {
            ctx->custom_header_value = header[i].value;
            break;
        }
    }

    // 5. 设置响应头
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_type.len = sizeof("application/json") - 1;
    r->headers_out.content_type.data = (u_char *) "application/json";

    // 6. 构建响应体 (JSON 格式)
    response.data = ngx_pcalloc(r->pool, NGX_MAX_ERROR_STR);
    if (response.data == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    response.len = ngx_snprintf(response.data, NGX_MAX_ERROR_STR,
                                "{\"user_agent\": \"%V\", \"custom_header\": \"%V\"}",
                                &ctx->custom_header_value, &ctx->custom_header_value)
                   - response.data;

    // 7. 分配缓冲区并设置响应体
    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if (b == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    b->pos = response.data;
    b->last = response.data + response.len;
    b->memory = 1; // 标记此缓冲区内容在内存中
    b->last_buf = 1; // 标记这是最后一个缓冲区

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

    // 8. 发送响应头
    r->headers_out.content_length_n = response.len;
    ngx_http_send_header(r);

    // 9. 发送响应体
    return ngx_http_output_filter(r, &out);
}

// 模块初始化函数
static ngx_int_t
ngx_http_hello_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    // 在 NGX_HTTP_CONTENT_PHASE 阶段注册我们的处理函数
    h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_hello_handler;

    return NGX_OK;
}

// 定义模块配置的创建函数(本例中未使用)
static void *
ngx_http_hello_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_hello_loc_conf_t  *conf;

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

    conf->enable = NGX_CONF_UNSET;
    return conf;
}

// 定义模块配置的合并函数(本例中未使用)
static char *
ngx_http_hello_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_hello_loc_conf_t *prev = parent;
    ngx_http_hello_loc_conf_t *conf = child;

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

    return NGX_CONF_OK;
}

// 定义模块的指令
static ngx_command_t ngx_http_hello_commands[] = {

    { ngx_string("hello_enable"),
      NGX_HTTP_LOC_CONF | NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_hello_loc_conf_t, enable),
      NULL },

      ngx_null_command
};

// 定义模块上下文
static ngx_http_module_t ngx_http_hello_module_ctx = {
    NULL,                          /* preconfiguration */
    ngx_http_hello_init,           /* postconfiguration */
    NULL,                          /* create main configuration */
    NULL,                          /* init main configuration */
    NULL,                          /* create server configuration */
    NULL,                          /* merge server configuration */
    ngx_http_hello_create_loc_conf, /* create location configuration */
    ngx_http_hello_merge_loc_conf   /* merge location configuration */
};

// 定义模块
ngx_module_t ngx_http_hello_module = {
    NGX_MODULE_V1,
    &ngx_http_hello_module_ctx,    /* module context */
    ngx_http_hello_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
};

4.2 编译配置文件 (config)

在与 ngx_http_hello_module.c 相同的目录下创建 config 文件:

ngx_addon_name=ngx_http_hello_module
HTTP_MODULES="$HTTP_MODULES ngx_http_hello_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_hello_module.c"

4.3 编译与安装模块

  1. ngx_http_hello_module.cconfig 文件放在同一个目录下,例如 /path/to/nginx-hello-module

  2. 进入 Nginx 源码目录,使用 --add-module 参数重新配置并编译:

    ./configure --add-module=/path/to/nginx-hello-module
    make
    sudo make install
    

4.4 配置 Nginx

nginx.conf 中,添加一个 location 块来启用我们的模块:

server {
    listen       80;
    server_name  localhost;

    location /hello {
        hello_enable on;
    }
}

4.5 测试模块

重启 Nginx 后,使用 curl 进行测试:

# 测试基本功能
curl http://localhost/hello

# 测试自定义请求头
curl -H "X-Custom-Header: MyValue" http://localhost/hello

你应该会看到类似如下的 JSON 响应:

{"user_agent": "curl/7.68.0", "custom_header": "MyValue"}

5. 代码详解:如何运用 ngx_http_request_t 字段

让我们回顾一下代码中是如何使用 ngx_http_request_t 的各个字段的:

  1. r->method:在 ngx_http_hello_handler 函数开头,我们通过 r->method != NGX_HTTP_GET 来判断请求方法,如果不是 GET 请求,则返回 405 Method Not Allowed

  2. r->pool:我们使用 ngx_pcalloc(r->pool, ...) 来分配模块上下文 ctx、响应体 response 和缓冲区 b 的内存。这确保了所有内存都会在请求结束后被自动回收。

  3. r->headers_in:通过 r->headers_in.user_agent 直接获取了解析后的 User-Agent 头。我们还遍历了 r->headers_in.headers 这个链表,来查找自定义的 X-Custom-Header,这展示了如何处理非标准的请求头。

  4. ngx_http_get_module_ctxngx_http_set_ctx:我们使用这两个宏来存取模块上下文 ctx。虽然在这个简单的例子中,我们只存储了一个字段,但在更复杂的模块中,ctx 可以用来在 body handlercleanup 等不同回调函数之间共享数据。

  5. r->headers_out:我们设置了 r->headers_out.statusNGX_HTTP_OK,并设置了 content_typeapplication/json。最后,通过 ngx_http_send_header(r) 将响应头发送给客户端。

  6. ngx_http_output_filter:这个函数是 Nginx 输出机制的入口。我们将包含 JSON 数据的缓冲区链 out 传递给它,Nginx 会负责将数据发送给客户端。

  7. ngx_http_get_module_loc_conf:虽然实战模块中未显式使用配置获取宏(因为配置结构体仅用于演示标准框架),但在真实项目中,handler 应通过 ngx_http_get_module_loc_conf(r, ngx_http_hello_module) 获取 location 级别的配置,从而根据指令值控制模块行为。这是 Nginx 模块配置驱动设计模式的核心。

  8. r->main->count 与异步处理:如果模块需要执行异步操作(如延迟响应、发起子请求),必须在异步操作前执行 r->main->count++ 防止请求被销毁,在异步回调完成后执行 r->main->count--。handler 应返回 NGX_AGAIN 而非 NGX_OK,告知 Nginx 请求尚未完成。本实战模块是同步处理的简单示例,未涉及异步逻辑,但理解这一机制对于编写高性能 Nginx 模块至关重要。

6. 总结

ngx_http_request_t 是 Nginx 模块开发的核心。通过本文,我们详细解析了其关键字段,并动手编写了一个完整的模块,演示了如何在实际代码中运用这些字段。掌握 ngx_http_request_t,你就掌握了与 Nginx HTTP 请求交互的钥匙,可以开始构建各种强大的自定义功能了。

希望这篇教程能帮助你开启 Nginx 模块开发之旅。如果你有任何问题或想法,欢迎在评论区交流。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐