子请求是Nginx内部嵌套调用机制,不走网络、不触发完整HTTP生命周期,需目标location声明internal;C模块须用ngx_http_subrequest API,Lua中对应ngx.location.capture;响应体需显式启用IN_MEMORY或回调处理。

子请求(subrequest)是 Nginx 模块开发中一种高效、安全的内部资源调用机制,它允许你在当前请求处理过程中,不经过网络、不暴露路径、不触发完整 HTTP 生命周期,直接向 Nginx 内部某个 location 发起嵌套请求,并获取其响应体或状态码。这在实现鉴权代理、动态配置注入、聚合式响应等场景中非常实用。
为什么用子请求而不是 openresty 的 ngx.location.capture?
如果你在 Lua 模块(如 OpenResty)中开发,ngx.location.capture 就是子请求的封装接口,本质完全一致;但若你是在 C 模块中开发(比如自研 filter 或 handler 模块),就必须直接调用 Nginx Core 提供的 C API:ngx_http_subrequest。它更底层、更轻量,也更可控——比如可指定是否继承父请求的变量、是否共享 request body、是否允许重定向等。
注意:子请求不是「转发」,也不是「反向代理」,它不走 upstream、不解析 DNS、不建 TCP 连接,全程在内存和 Nginx 事件循环内完成。
关键步骤:发起一个标准子请求
在 C 模块中调用 ngx_http_subrequest 需要准备四个核心参数:
- request:当前主请求指针(r)
- uri:目标 internal location 的 URI(必须以 / 开头,且该 location 必须声明为 internal)
- args:可选查询参数(如 &uid=123),传入 ngx_str_t 结构
- psr:输出参数,接收子请求返回的 request 结构体指针(用于后续挂回调)
示例代码片段(简化):
ngx_http_request_t *sr;
ngx_str_t uri = ngx_string("/_fetch_meta");
ngx_str_t args = ngx_null_string;
<p>rc = ngx_http_subrequest(r, &uri, &args, &sr,
NULL, NGX_HTTP_SUBREQUEST_IN_MEMORY);
if (rc == NGX_OK) {
sr->write_event_handler = ngx_http_subrequest_done;
}
⚠️ 必须确保目标 location 声明为 internal,否则 Nginx 会直接返回 404 或 403:
location = /_fetch_meta {
internal;
proxy_pass http://backend/meta;
# 或由其他 handler(如 ngx_http_empty_gif_module)生成
}
如何安全地读取子请求响应内容
子请求默认不自动读取响应体。你需要显式启用 NGX_HTTP_SUBREQUEST_IN_MEMORY 标志,这样 Nginx 会将响应体缓存到 sr->upstream->buffer 中;或者使用回调函数监听子请求结束事件,在 psr->write_event_handler 或 psr->read_event_handler 中处理。
常见做法是设置自定义回调:
- 在 subrequest 创建后,把主请求 r 存入 sr->data,便于回调时访问上下文
- 在回调函数中,检查 sr->headers_out.status 和 sr->upstream->buffer.pos ~ .last 获取原始响应体
- 避免直接 memcpy 大响应体;如需多次使用,建议 copy 到 r->pool 分配的内存中
典型实战场景与避坑提醒
子请求常用于以下模块逻辑:
- 统一鉴权模块:在 access phase 向 /_check_token 发起子请求,根据返回 200/403 决定是否放行
- Header 注入模块:在 header_filter 阶段调用 /_inject_headers,将 JSON 响应转为 set_header 指令
- 错误页增强:error_page 404 /_fallback,再由子请求决定是渲染静态页还是调用 fallback service
⚠️ 容易踩的坑:
- 忘记加 internal —— 导致子请求被拒绝
- 在 content phase 多次调用 subrequest 但未限制并发数 —— 可能触发连接数超限或栈溢出
- 子请求返回大 body 却未设 buffer_size —— 导致截断或内存暴涨
- 在子请求回调中修改主请求的 headers_out —— 必须在 send_header 之前,否则无效

















