7. Nginx subrequest 机制源码分析与模块开发实战
1. 引言
摘要:本文从 Nginx 模块开发视角,深入剖析 subrequest 子请求机制的源码实现。内容涵盖
ngx_http_request_t核心数据结构、ngx_http_subrequest创建函数的调用链、响应捕获机制(ngx_http_send_special与完成回调),以及引用计数与内存池的生命周期管理。文章提供两个完整的 C 模块开发示例:单个 subrequest 的发起与响应捕获、并行 subrequest 聚合模块(BFF 场景),并附有 config 编译配置、nginx.conf 配置示例及 GDB 调试技巧,帮助读者掌握在自定义 Nginx 模块中使用 subrequest 的完整能力。
Nginx 的 subrequest(子请求)机制是其内部请求处理的核心特性之一。与普通 HTTP 请求不同,subrequest 不经过网络层,而是在 Nginx 事件循环内部直接创建和处理,主请求(main request)可以发起一个或多个子请求,并将子请求的响应捕获后用于权限校验、内容聚合、动态组装等场景。
本文将从 Nginx 模块开发 的视角出发,深入分析 subrequest 的源码实现,包括 ngx_http_subrequest 函数的调用链、请求上下文传递机制、响应捕获方式,并给出完整的 C 模块开发示例,帮助读者掌握在自定义 Nginx 模块中使用 subrequest 的能力。
2. subrequest 源码核心数据结构
2.1 请求对象 ngx_http_request_t
在 Nginx 源码 src/http/ngx_http_request.h 中,每个 HTTP 请求由一个 ngx_http_request_t 结构体表示。subrequest 相关的关键字段如下:
// src/http/ngx_http_request.h(简化)
typedef struct ngx_http_request_s {
// 主请求指针,subrequest 指向其父请求
struct ngx_http_request_s *main;
// 父请求指针,subrequest 指向发起者
struct ngx_http_request_s *parent;
// 请求方法(GET/POST 等)
ngx_uint_t method;
// 请求 URI
ngx_str_t uri;
// 请求参数 args
ngx_str_t args;
// 请求头
ngx_http_headers_in_t headers_in;
// 响应头
ngx_http_headers_out_t headers_out;
// 响应体链表
ngx_chain_t *out;
// 请求池,subrequest 拥有独立的内存池
ngx_pool_t *pool;
// 请求阶段
ngx_uint_t phase_handler;
// 标志位:是否为 subrequest
unsigned subrequest:1;
// 标志位:父请求是否已完成(用于 subrequest 完成时的判断)
unsigned parent_done:1;
// ... 其他字段
} ngx_http_request_t;
关键设计:
main指向整个请求链的根请求,parent指向直接发起者。- 每个 subrequest 拥有独立的
pool(内存池),subrequest 完成后可独立释放。 subrequest:1标志位用于区分主请求与子请求。
2.2 subrequest 的创建函数 ngx_http_subrequest
subrequest 的创建入口在 src/http/ngx_http_core_module.c 中:
// src/http/ngx_http_core_module.c
ngx_int_t
ngx_http_subrequest(ngx_http_request_t *r,
ngx_str_t *uri, ngx_str_t *args,
ngx_http_request_t **psr,
ngx_http_post_subrequest_t *ps,
ngx_uint_t flags)
{
ngx_http_request_t *sr;
ngx_http_core_srv_conf_t *cscf;
// 1. 从主请求的内存池中分配 subrequest 结构体
sr = ngx_pcalloc(r->pool, sizeof(ngx_http_request_t));
if (sr == NULL) {
return NGX_ERROR;
}
// 2. 设置父子关系
sr->main = r->main ? r->main : r;
sr->parent = r;
r->main->count++;
// 3. 复制关键字段
sr->method = NGX_HTTP_GET;
sr->uri = *uri;
sr->args = *args;
sr->schema = r->schema;
// 4. 设置 subrequest 标志
sr->subrequest = 1;
// 5. 创建独立的内存池
sr->pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, r->connection->log);
if (sr->pool == NULL) {
return NGX_ERROR;
}
// 6. 复制请求头(可选)
sr->headers_in = r->headers_in;
// 7. 注册完成回调
if (ps) {
sr->post_subrequest = ps;
}
// 8. 将 subrequest 加入事件循环
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
ngx_http_handler(sr, cscf);
*psr = sr;
return NGX_OK;
}
源码分析要点:
- subrequest 不创建新的 TCP 连接,直接从连接的空闲请求池分配。
sr->main->count++增加主请求的引用计数,确保主请求在所有 subrequest 完成前不会结束。ngx_http_handler将 subrequest 加入 Nginx 的阶段处理流程,与主请求走相同的处理管道。
3. 开发第一个 subrequest 模块:从源码编译
3.1 模块目录结构
my_subrequest_module/
├── config # Nginx 模块编译配置
└── ngx_http_my_subrequest_module.c # 模块源码
3.2 config 文件
# my_subrequest_module/config
ngx_addon_name=ngx_http_my_subrequest_module
HTTP_MODULES="$HTTP_MODULES ngx_http_my_subrequest_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_my_subrequest_module.c"
3.3 模块源码:发起 subrequest 并捕获响应
// my_subrequest_module/ngx_http_my_subrequest_module.c
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
// 模块上下文结构体
typedef struct {
ngx_str_t backend_uri;
} ngx_http_my_subrequest_loc_conf_t;
// 前向声明
static ngx_int_t ngx_http_my_subrequest_handler(ngx_http_request_t *r);
static void *ngx_http_my_subrequest_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_my_subrequest_merge_loc_conf(ngx_conf_t *cf,
void *parent, void *child);
// subrequest 完成回调函数
static ngx_int_t
ngx_http_my_subrequest_post_handler(ngx_http_request_t *sr, void *data,
ngx_int_t rc)
{
ngx_http_request_t *r = data; // 主请求
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"subrequest completed with status %i, body length %z",
sr->headers_out.status, sr->out ? ngx_chain_length(sr->out) : 0);
// 将 subrequest 的响应体复制到主请求的输出链
if (sr->out) {
// 注意:这里简化处理,实际需要将响应体数据复制到主请求的 pool 中,避免 subrequest 释放后内存失效
r->out = sr->out;
r->headers_out.content_length_n = sr->headers_out.content_length_n;
}
r->headers_out.status = sr->headers_out.status;
return NGX_OK;
}
// 模块主处理函数
static ngx_int_t
ngx_http_my_subrequest_handler(ngx_http_request_t *r)
{
ngx_http_my_subrequest_loc_conf_t *mlcf;
ngx_http_request_t *sr;
ngx_http_post_subrequest_t *ps;
ngx_int_t rc;
// 只处理主请求,避免 subrequest 递归
if (r->subrequest) {
return NGX_DECLINED;
}
mlcf = ngx_http_get_module_loc_conf(r, ngx_http_my_subrequest_module);
// 1. 分配完成回调结构体
ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
if (ps == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ps->handler = ngx_http_my_subrequest_post_handler;
ps->data = r;
// 2. 发起 subrequest
rc = ngx_http_subrequest(r, &mlcf->backend_uri, NULL, &sr, ps,
NGX_HTTP_SUBREQUEST_WAITED);
if (rc != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
// 3. 挂起主请求,等待 subrequest 完成
ngx_http_set_ctx(r, sr, ngx_http_my_subrequest_module);
return NGX_DONE;
}
// 配置指令定义
static ngx_command_t ngx_http_my_subrequest_commands[] = {
{ ngx_string("my_backend_uri"),
NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_my_subrequest_loc_conf_t, backend_uri),
NULL },
ngx_null_command
};
// 模块上下文
static ngx_http_module_t ngx_http_my_subrequest_module_ctx = {
NULL, // preconfiguration
NULL, // postconfiguration
NULL, // create main configuration
NULL, // init main configuration
NULL, // create server configuration
NULL, // merge server configuration
ngx_http_my_subrequest_create_loc_conf, // create location configuration
ngx_http_my_subrequest_merge_loc_conf // merge location configuration
};
// 模块定义
ngx_module_t ngx_http_my_subrequest_module = {
NGX_MODULE_V1,
&ngx_http_my_subrequest_module_ctx, // module context
ngx_http_my_subrequest_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 void *
ngx_http_my_subrequest_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_my_subrequest_loc_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_my_subrequest_loc_conf_t));
if (conf == NULL) {
return NULL;
}
ngx_str_set(&conf->backend_uri, "/internal/default");
return conf;
}
// 配置结构体合并
static char *
ngx_http_my_subrequest_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_my_subrequest_loc_conf_t *prev = parent;
ngx_http_my_subrequest_loc_conf_t *conf = child;
ngx_conf_merge_str_value(conf->backend_uri, prev->backend_uri,
"/internal/default");
return NGX_CONF_OK;
}
3.4 编译模块
# 下载 Nginx 源码
wget http://nginx.org/download/nginx-1.26.0.tar.gz
tar -zxvf nginx-1.26.0.tar.gz
cd nginx-1.26.0
# 配置时添加自定义模块
./configure \
--prefix=/usr/local/nginx \
--add-module=/path/to/my_subrequest_module
make -j$(nproc)
sudo make install
3.5 nginx.conf 配置示例
server {
listen 80;
server_name example.com;
location / {
my_backend_uri /internal/data;
# 模块处理函数会自动发起 subrequest
}
location /internal/data {
internal;
return 200 '{"status": "ok", "data": "from subrequest"}';
add_header Content-Type application/json;
}
}
4. subrequest 响应捕获机制源码分析
4.1 响应体传递:ngx_http_send_special
subrequest 的响应体通过 Nginx 的过滤器链传递。关键源码在 src/http/ngx_http_request.c:
// src/http/ngx_http_request.c
ngx_int_t
ngx_http_send_special(ngx_http_request_t *r, ngx_uint_t flags)
{
ngx_chain_t *cl;
ngx_buf_t *b;
// 创建特殊标记的 buffer
b = ngx_calloc_buf(r->pool);
if (b == NULL) {
return NGX_ERROR;
}
// 设置 subrequest 完成标记
if (flags & NGX_HTTP_LAST) {
b->last_buf = 1; // 标记为最后一个 buffer
}
cl = ngx_alloc_chain_link(r->pool);
if (cl == NULL) {
return NGX_ERROR;
}
cl->buf = b;
cl->next = NULL;
// 通过过滤器链发送
return ngx_http_output_filter(r, cl);
}
当 subrequest 的过滤器链处理完所有响应体后,会发送一个带有 last_buf 标记的 buffer,触发 ngx_http_finalize_request 完成 subrequest。
4.2 完成回调触发机制
// src/http/ngx_http_request.c(简化)
void
ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
{
// 如果是 subrequest,调用 post_subrequest 回调
if (r->subrequest && r->post_subrequest) {
rc = r->post_subrequest->handler(r, r->post_subrequest->data, rc);
}
// 减少主请求引用计数
if (r != r->main) {
r->main->count--;
}
// 如果主请求引用计数为 0,真正结束主请求
if (r->main->count == 0) {
ngx_http_finalize_request(r->main, NGX_DONE);
}
}
关键设计:
- subrequest 结束时调用
post_subrequest->handler,将控制权交回主请求。 - 主请求通过
count引用计数管理 subrequest 的生命周期,跟踪所有活跃的 subrequest。 - 只有当所有 subrequest 都完成后,主请求才会真正结束。
5. 实战:并行 subrequest 聚合模块
5.1 场景描述
构建一个 BFF(Backend for Frontend)模块,并行发起多个 subrequest 获取用户信息和订单数据,然后组装成统一的 JSON 响应。
5.2 模块源码:使用 ngx_http_subrequest 并行调用
// ngx_http_aggregate_module.c(核心片段)
/*
* 聚合上下文结构体 —— 用于在多个 subrequest 之间共享状态。
* 每个主请求对应一个 ctx 实例,贯穿整个并行请求的生命周期。
*/
typedef struct {
ngx_http_request_t *requests[2]; // 保存两个 subrequest 的请求指针,用于在回调中区分是哪个子请求完成
ngx_str_t responses[2]; // 分别存储两个 subrequest 的响应体字符串
ngx_uint_t completed; // 已完成的 subrequest 计数,当等于 2 时触发最终组装
ngx_http_request_t *main_request; // 保存主请求指针,回调中通过它发送最终响应
} ngx_http_aggregate_ctx_t;
/*
* subrequest 完成回调函数 —— 每个 subrequest 结束时自动调用。
* 参数 sr:刚刚完成的 subrequest 请求对象
* 参数 data:发起 subrequest 时传入的 ctx 指针
* 参数 rc:subrequest 的结束状态码
*/
static ngx_int_t
ngx_http_aggregate_post_handler(ngx_http_request_t *sr, void *data,
ngx_int_t rc)
{
ngx_http_aggregate_ctx_t *ctx = data; // 恢复聚合上下文
ngx_http_request_t *r = ctx->main_request; // 获取主请求
ngx_uint_t i;
/* 步骤一:遍历 requests 数组,定位当前完成的 subrequest 是哪一个 */
for (i = 0; i < 2; i++) {
if (ctx->requests[i] == sr) {
/* 步骤二:保存 subrequest 的响应体到 ctx 中 */
if (sr->out) {
/*
* 注意:sr->out 属于 subrequest 的内存池,subrequest 结束后
* 该内存池可能被销毁,因此必须将数据复制到主请求的内存池中。
* 这里先分配主请求 pool 的内存,实际生产代码需要遍历链表逐段复制。
*/
ctx->responses[i].data = ngx_palloc(r->pool,
ngx_chain_length(sr->out) + 1);
// 实际需要遍历链表复制,此处为简化处理
}
break; // 找到后退出循环
}
}
ctx->completed++; // 递增已完成计数
/* 步骤三:检查是否所有 subrequest 都已完成,若是则组装最终响应 */
if (ctx->completed == 2) {
ngx_str_t json;
/*
* 在主请求的 pool 中分配 JSON 组装缓冲区。
* 使用主请求 pool 确保数据在发送完成前始终有效。
*/
json.data = ngx_palloc(r->pool, 256);
json.len = ngx_sprintf(json.data,
"{\"user\":%V,\"orders\":%V}",
&ctx->responses[0], &ctx->responses[1]) - json.data;
/* 设置响应状态码和 Content-Length 头 */
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = json.len;
ngx_http_send_header(r); // 发送响应头
/* 构造响应体 buffer 并挂接到输出链 */
ngx_buf_t *b = ngx_create_temp_buf(r->pool, json.len);
b->last = ngx_copy(b->pos, json.data, json.len);
ngx_chain_t *cl = ngx_alloc_chain_link(r->pool);
cl->buf = b;
cl->next = NULL;
/* 通过过滤器链发送最终响应给客户端 */
return ngx_http_output_filter(r, cl);
}
return NGX_OK; // 还有 subrequest 未完成,继续等待
}
/*
* 模块主处理函数 —— 入口函数,负责初始化上下文并并行发起 subrequest。
* 注意:该函数返回 NGX_DONE 表示主请求被挂起,等待 subrequest 完成后恢复。
*/
static ngx_int_t
ngx_http_aggregate_handler(ngx_http_request_t *r)
{
ngx_http_aggregate_ctx_t *ctx;
ngx_http_post_subrequest_t *ps[2]; // 两个 subrequest 的完成回调结构体
ngx_str_t uris[2] = {
ngx_string("/internal/user_info"), // 第一个 subrequest 的目标 URI
ngx_string("/internal/order_stats") // 第二个 subrequest 的目标 URI
};
ngx_int_t rc;
/* 步骤一:获取或创建聚合上下文(分配上下文) */
ctx = ngx_http_get_module_ctx(r, ngx_http_aggregate_module);
if (ctx == NULL) {
/*
* 首次进入时,在主请求的 pool 中分配上下文结构体。
* 使用主请求 pool 确保 ctx 在整个请求生命周期内有效。
*/
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_aggregate_ctx_t));
if (ctx == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR; // 内存分配失败
}
ngx_http_set_ctx(r, ctx, ngx_http_aggregate_module); // 将 ctx 绑定到主请求
}
ctx->main_request = r; // 保存主请求指针,供回调函数使用
/* 步骤二:并行发起两个 subrequest(发起并行请求) */
for (ngx_uint_t i = 0; i < 2; i++) {
/*
* 为每个 subrequest 分配回调结构体。
* 注意:必须在主请求 pool 中分配,因为 subrequest 完成后其 pool 可能被销毁。
*/
ps[i] = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
ps[i]->handler = ngx_http_aggregate_post_handler; // 设置完成回调函数
ps[i]->data = ctx; // 传入 ctx,回调中通过 data 恢复上下文
/*
* 调用 ngx_http_subrequest 创建 subrequest。
* 参数说明:
* r - 主请求(父请求)
* &uris[i] - subrequest 的目标 URI
* NULL - 无查询参数
* &ctx->requests[i] - 输出参数,返回 subrequest 的请求指针
* ps[i] - 完成回调结构体
* NGX_HTTP_SUBREQUEST_WAITED - 标志位:父请求是否已完成(用于 subrequest 完成时的判断)
*/
rc = ngx_http_subrequest(r, &uris[i], NULL, &ctx->requests[i],
ps[i], NGX_HTTP_SUBREQUEST_WAITED);
if (rc != NGX_OK) {
/*
* 注意:如果某个 subrequest 创建失败,应清理已创建的 subrequest。
* 生产代码中需要遍历已创建的 subrequest 并调用 ngx_http_finalize_request 清理。
*/
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
}
/*
* 返回 NGX_DONE 挂起主请求的处理流程。
* Nginx 事件循环会继续调度 subrequest,当所有 subrequest 完成后,
* 通过回调函数 ngx_http_aggregate_post_handler 恢复主请求并发送响应。
*/
return NGX_DONE;
}
5.3 配置示例
server {
listen 80;
server_name api.example.com;
location /dashboard {
aggregate_module on;
}
location /internal/user_info {
internal;
proxy_pass http://user_service:8083;
}
location /internal/order_stats {
internal;
proxy_pass http://order_service:8084;
}
}
6. subrequest 与主请求的生命周期管理
6.1 引用计数机制
Nginx 通过 r->main->count 管理 subrequest 的生命周期。每次创建 subrequest 时 count++,每次 subrequest 完成时 count--。只有当 count == 0 时,主请求才会真正结束。
// 创建 subrequest 时
sr->main = r->main ? r->main : r;
r->main->count++;
// subrequest 完成时
if (r != r->main) {
r->main->count--;
}
if (r->main->count == 0) {
ngx_http_finalize_request(r->main, NGX_DONE);
}
6.2 内存池管理
每个 subrequest 拥有独立的内存池,subrequest 完成后可以立即释放,不阻塞主请求的内存回收:
// subrequest 完成时释放内存池
if (r->pool) {
ngx_destroy_pool(r->pool);
r->pool = NULL;
}
7. 源码级调试技巧
7.1 使用 GDB 调试 subrequest
# 编译时保留调试符号
./configure --with-debug --prefix=/usr/local/nginx
make -j$(nproc)
sudo make install
# GDB 调试
gdb /usr/local/nginx/sbin/nginx
(gdb) set follow-fork-mode child
(gdb) b ngx_http_subrequest
(gdb) b ngx_http_finalize_request
(gdb) run
7.2 关键断点位置
| 函数 | 文件 | 行号(1.26.0) | 作用 |
|---|---|---|---|
ngx_http_subrequest |
ngx_http_core_module.c |
~1800 | 创建和处理 subrequest |
ngx_http_finalize_request |
ngx_http_request.c |
~2900 | 结束请求 |
ngx_http_send_special |
ngx_http_request.c |
~2100 | 发送完成标记 |
ngx_http_handler |
ngx_http_core_module.c |
~900 | 进入阶段处理 |
7.3 日志调试
// 在模块中添加调试日志
ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0,
"subrequest created: uri=%V, main=%p, parent=%p",
&sr->uri, sr->main, sr->parent);
ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0,
"subrequest completed: count=%ui", r->main->count);
8. 注意事项与最佳实践
8.1 性能考虑
- 使用 NGX_HTTP_SUBREQUEST_WAITED 标志时,主请求会同步等待 subrequest 完成。使用
NGX_HTTP_SUBREQUEST_WAITED标志让主请求等待 subrequest 完成。 - 多个 subrequest 应使用
ngx_http_subrequest依次发起,Nginx 的事件循环会自动调度。 - 避免 subrequest 嵌套过深,建议不超过 3 层。
8.2 内存安全
- subrequest 的响应体数据属于 subrequest 的内存池,在主请求中使用前需要复制到主请求的内存池中。
- 使用
ngx_palloc(r->pool, ...)分配主请求内存,使用ngx_palloc(sr->pool, ...)分配 subrequest 内存。
8.3 错误处理
- subrequest 可能返回
NGX_HTTP_NOT_FOUND等错误码,需要在回调中检查sr->headers_out.status。 - 使用
NGX_HTTP_SUBREQUEST_BACKGROUND标志可以让 subrequest 在后台运行,主请求不等待。使用 NGX_HTTP_SUBREQUEST_WAITED 标志时,主请求会同步等待 subrequest 完成。
9. 总结
本文从 Nginx 模块开发的角度,深入分析了 subrequest 的源码实现。核心要点包括:
- 数据结构:
ngx_http_request_t通过main、parent指针构建请求树,subrequest标志位区分请求类型。 - 创建函数:
ngx_http_subrequest从连接池分配请求结构体,设置父子关系,通过ngx_http_handler加入处理流程。 - 生命周期:引用计数
count管理所有 subrequest 的完成状态,独立内存池支持 subrequest 的独立释放,避免内存泄漏。 - 回调机制:
post_subrequest回调函数在 subrequest 完成时被调用,将控制权交回主请求。 - 实战开发:通过自定义模块示例,展示了如何发起 subrequest、捕获响应、并行聚合数据。
掌握 subrequest 的源码级原理,可以帮助开发者编写高性能的 Nginx C 模块,实现复杂的请求处理逻辑。
更多推荐




所有评论(0)