5. Nginx 模块开发入门:深入理解 ngx_http_request_t 结构体
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=18,args为name=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_GET、NGX_HTTP_POST。通常用这个字段做判断,效率更高。 -
uri(ngx_str_t):请求的 URI,不包含查询参数。例如,请求/api/user?name=test,uri为/api/user。 -
http_protocol(ngx_str_t):HTTP 协议版本,例如"HTTP/1.1"、"HTTP/2.0"。 -
http_version(ngx_uint_t):HTTP 协议版本的枚举值,例如NGX_HTTP_VERSION_11、NGX_HTTP_VERSION_20。
3.2 请求头部
headers_in(ngx_http_headers_in_t):这是一个结构体,包含了所有解析后的请求头。例如:headers_in->host:Host头。headers_in->content_type:Content-Type头。headers_in->content_length_n:Content-Length头的数值。headers_in->user_agent:User-Agent头。headers_in->cookies:Cookie头(以链表形式存储)。
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 *):指向请求体结构的指针。当需要读取POST或PUT请求的 body 时使用。request_body->buf:指向存储请求体数据的缓冲区。request_body->temp_file:如果请求体太大,Nginx 会将其暂存到临时文件中,此字段指向该文件。
3.4 连接与上下文
connection(ngx_connection_t *):指向底层 TCP 连接的指针。通过它可以获取客户端 IP、端口等信息。connection->addr_text:客户端的 IP 地址和端口。
ctx(void *):这是一个非常重要的字段,用于在模块内部的不同处理阶段(如init、body handler、finalize)之间共享数据。通常,我们会定义一个模块专用的上下文结构体,然后通过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)");
}
// 继续处理请求...
}
注意事项:
- 编译依赖:使用 OpenSSL API 需要在模块源码中包含
<openssl/ssl.h>头文件,并在编译时链接 OpenSSL 库。Nginx 在./configure --with-http_ssl_module时会自动处理这些依赖。 - 空指针检查:在访问
r->connection->ssl之前,务必检查其是否为NULL,因为非 HTTPS 请求下该字段为NULL,直接访问会导致段错误。 - 握手阶段:在
NGX_HTTP_SSL_HANDSHAKE_PHASE阶段之前,SSL 握手可能尚未完成,此时r->connection->ssl->handshaked为0。在 content handler 中(如本例),握手通常已完成,可以安全访问 SSL 信息。 - 性能影响:频繁调用 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_PHASE、NGX_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_conf、r->srv_conf 和 r->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 的配置合并遵循「子层级覆盖父层级」的原则:
- 如果 location 块中显式设置了某指令,则使用 location 级别的值。
- 如果 location 块未设置,则继承 server 级别的值。
- 如果 server 也未设置,则使用 main 级别的默认值(由
create_loc_conf中设定的初始值决定)。
这种机制使得模块配置既灵活又简洁——用户只需在需要覆盖的地方显式指定指令即可。
3.7 异步处理的具体做法
Nginx 的高性能核心在于其事件驱动、非阻塞的异步处理模型。在模块开发中,如果 handler 中需要执行耗时操作(如数据库查询、外部 HTTP 请求、文件 I/O),绝不能阻塞当前进程,而应使用 Nginx 提供的异步机制。
异步处理的基本流程
Nginx 的异步处理通常遵循以下步骤:
- 增加请求引用计数:使用
r->main->count++防止请求在异步等待期间被意外销毁。 - 注册事件处理函数:通过
ngx_add_timer或ngx_add_event注册一个超时或 I/O 事件。 - 返回
NGX_AGAIN:告诉 Nginx 当前请求尚未完成,后续会通过事件回调继续处理。 - 在事件回调中恢复处理:当事件触发时,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;
}
异步处理的关键注意事项
- 引用计数管理:每次发起异步操作前必须
r->main->count++,操作完成后必须r->main->count--。计数归零时 Nginx 会销毁请求,过早归零会导致野指针。 - 内存安全:异步回调中访问的
r指针必须确保请求仍然存活。建议在回调中通过事件结构体的data字段传递请求指针,而不是使用全局变量。 - 错误处理:如果异步操作失败(如分配内存失败),必须及时减少引用计数并返回错误,避免请求泄漏。
- 不要在回调中阻塞:异步回调本身仍然在 Nginx 的事件循环中执行,同样不能执行阻塞操作。如果需要再次等待,可以继续注册新的事件。
异步处理流程总览
下图以定时器异步为例,展示了 Nginx 模块异步处理的完整流程与数据流向:
流程说明:
- 请求进入 handler:Nginx 在 content phase 调用模块的 handler 函数。
- 增加引用计数:
r->main->count++防止请求在异步等待期间被销毁。 - 注册事件:通过
ngx_add_timer注册超时事件,并将请求指针r存入ev->data。 - 返回
NGX_AGAIN:告知 Nginx 当前请求尚未完成,后续由事件回调继续处理。 - 事件循环等待:Nginx 继续处理其他事件,不阻塞当前进程。
- 事件触发:定时器超时后,Nginx 调用注册的回调函数。
- 恢复请求指针:回调中通过
ev->data取回请求指针r。 - 构建并发送响应:设置响应头、分配缓冲区、调用
ngx_http_output_filter发送数据。 - 减少引用计数:
r->main->count--,允许 Nginx 在响应发送完成后销毁请求。 - 请求完成:引用计数归零,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 编译与安装模块
-
将
ngx_http_hello_module.c和config文件放在同一个目录下,例如/path/to/nginx-hello-module。 -
进入 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 的各个字段的:
-
r->method:在ngx_http_hello_handler函数开头,我们通过r->method != NGX_HTTP_GET来判断请求方法,如果不是 GET 请求,则返回405 Method Not Allowed。 -
r->pool:我们使用ngx_pcalloc(r->pool, ...)来分配模块上下文ctx、响应体response和缓冲区b的内存。这确保了所有内存都会在请求结束后被自动回收。 -
r->headers_in:通过r->headers_in.user_agent直接获取了解析后的User-Agent头。我们还遍历了r->headers_in.headers这个链表,来查找自定义的X-Custom-Header,这展示了如何处理非标准的请求头。 -
ngx_http_get_module_ctx和ngx_http_set_ctx:我们使用这两个宏来存取模块上下文ctx。虽然在这个简单的例子中,我们只存储了一个字段,但在更复杂的模块中,ctx可以用来在body handler、cleanup等不同回调函数之间共享数据。 -
r->headers_out:我们设置了r->headers_out.status为NGX_HTTP_OK,并设置了content_type为application/json。最后,通过ngx_http_send_header(r)将响应头发送给客户端。 -
ngx_http_output_filter:这个函数是 Nginx 输出机制的入口。我们将包含 JSON 数据的缓冲区链out传递给它,Nginx 会负责将数据发送给客户端。 -
ngx_http_get_module_loc_conf:虽然实战模块中未显式使用配置获取宏(因为配置结构体仅用于演示标准框架),但在真实项目中,handler 应通过ngx_http_get_module_loc_conf(r, ngx_http_hello_module)获取 location 级别的配置,从而根据指令值控制模块行为。这是 Nginx 模块配置驱动设计模式的核心。 -
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 模块开发之旅。如果你有任何问题或想法,欢迎在评论区交流。
更多推荐




所有评论(0)