- 新增 lua-embed-analysis.md 技术分析文档 - 新增 lua-nginx-module 文档目录 - 更新 gitignore 允许跟踪 docs/lua-nginx-module/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
200 lines
4.4 KiB
Markdown
200 lines
4.4 KiB
Markdown
# lua-nginx-module Subrequest 内部请求
|
||
|
||
本文档详细说明 lua-nginx-module 的子请求功能。
|
||
|
||
---
|
||
|
||
## 一、核心文件
|
||
|
||
| 文件 | 说明 |
|
||
|------|------|
|
||
| `src/ngx_http_lua_subrequest.c` | 子请求实现 |
|
||
| `src/ngx_http_lua_capturefilter.c` | 响应捕获过滤器 |
|
||
|
||
---
|
||
|
||
## 二、API 概述
|
||
|
||
### `ngx.location.capture(uri, options?)`
|
||
|
||
发起单个子请求。
|
||
|
||
### `ngx.location.capture_multi(requests)`
|
||
|
||
并发发起多个子请求。
|
||
|
||
**关键发现**: `capture` 实际上是 `capture_multi` 的薄包装。
|
||
|
||
---
|
||
|
||
## 三、支持的 HTTP 方法
|
||
|
||
| 常量 | 方法 |
|
||
|------|------|
|
||
| `ngx.HTTP_GET` | GET |
|
||
| `ngx.HTTP_POST` | POST |
|
||
| `ngx.HTTP_PUT` | PUT |
|
||
| `ngx.HTTP_DELETE` | DELETE |
|
||
| `ngx.HTTP_HEAD` | HEAD |
|
||
| `ngx.HTTP_PATCH` | PATCH |
|
||
| `ngx.HTTP_OPTIONS` | OPTIONS |
|
||
|
||
---
|
||
|
||
## 四、选项参数
|
||
|
||
```lua
|
||
local res = ngx.location.capture(uri, {
|
||
method = ngx.HTTP_POST, -- HTTP 方法
|
||
args = "foo=bar", -- 参数字符串或 table
|
||
body = '{"data":1}', -- 请求体
|
||
headers = { -- 请求头
|
||
["Content-Type"] = "application/json"
|
||
},
|
||
vars = { -- 变量
|
||
upstream = "backend"
|
||
},
|
||
share_all_vars = false, -- 共享所有变量
|
||
copy_all_vars = false, -- 复制所有变量
|
||
always_forward_body = false, -- 始终转发请求体
|
||
ctx = {} -- 传递上下文
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
## 五、响应结构
|
||
|
||
```lua
|
||
local res = ngx.location.capture("/api")
|
||
-- res = {
|
||
-- status = 200, -- HTTP 状态码
|
||
-- header = {...}, -- 响应头 table
|
||
-- body = "...", -- 响应体
|
||
-- truncated = false -- 是否被截断
|
||
-- }
|
||
```
|
||
|
||
---
|
||
|
||
## 六、使用示例
|
||
|
||
### 6.1 基本用法
|
||
|
||
```lua
|
||
local res = ngx.location.capture("/internal/users")
|
||
if res.status == 200 then
|
||
local users = cjson.decode(res.body)
|
||
ngx.say("Users: ", #users)
|
||
end
|
||
```
|
||
|
||
### 6.2 POST 请求
|
||
|
||
```lua
|
||
local res = ngx.location.capture("/api/create", {
|
||
method = ngx.HTTP_POST,
|
||
body = '{"name":"john"}',
|
||
headers = {
|
||
["Content-Type"] = "application/json"
|
||
}
|
||
})
|
||
```
|
||
|
||
### 6.3 并发请求
|
||
|
||
```lua
|
||
local res1, res2, res3 = ngx.location.capture_multi({
|
||
{"/api/users"},
|
||
{"/api/products", {method = ngx.HTTP_GET}},
|
||
{"/api/orders", {args = "status=pending"}}
|
||
})
|
||
|
||
ngx.say("Users: ", res1.status)
|
||
ngx.say("Products: ", res2.status)
|
||
ngx.say("Orders: ", res3.status)
|
||
```
|
||
|
||
---
|
||
|
||
## 七、内部实现
|
||
|
||
### 7.1 数据存储结构
|
||
|
||
```c
|
||
struct ngx_http_lua_co_ctx_s {
|
||
ngx_int_t *sr_statuses; // 子请求状态码数组
|
||
ngx_http_headers_out_t **sr_headers; // 子请求响应头数组
|
||
ngx_str_t *sr_bodies; // 子请求响应体数组
|
||
uint8_t *sr_flags; // 子请求标志位数组
|
||
|
||
unsigned nsubreqs; // 子请求总数
|
||
unsigned pending_subreqs; // 待处理子请求数
|
||
};
|
||
```
|
||
|
||
### 7.2 响应捕获过滤器
|
||
|
||
```c
|
||
// 头部过滤器
|
||
static ngx_int_t ngx_http_lua_capture_header_filter(ngx_http_request_t *r)
|
||
{
|
||
if (ctx && ctx->capture) {
|
||
r->filter_need_in_memory = 1; // 强制内存缓冲
|
||
return NGX_OK; // 拦截,不发送到客户端
|
||
}
|
||
}
|
||
|
||
// 响应体过滤器
|
||
static ngx_int_t ngx_http_lua_capture_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
|
||
{
|
||
// 将响应体复制到 ctx->body 链表
|
||
}
|
||
```
|
||
|
||
### 7.3 响应组装
|
||
|
||
```c
|
||
static void ngx_http_lua_handle_subreq_responses(...)
|
||
{
|
||
for (index = 0; index < coctx->nsubreqs; index++) {
|
||
lua_createtable(co, 0, 4); // 创建响应表
|
||
|
||
// status
|
||
lua_pushinteger(co, coctx->sr_statuses[index]);
|
||
lua_setfield(co, -2, "status");
|
||
|
||
// truncated
|
||
if (coctx->sr_flags[index] & NGX_HTTP_LUA_SUBREQ_TRUNCATED) {
|
||
lua_pushboolean(co, 1);
|
||
lua_setfield(co, -2, "truncated");
|
||
}
|
||
|
||
// body
|
||
lua_pushlstring(co, body_str->data, body_str->len);
|
||
lua_setfield(co, -2, "body");
|
||
|
||
// header
|
||
// ...
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 八、请求体传递规则
|
||
|
||
| 条件 | 行为 |
|
||
|------|------|
|
||
| 指定了 `body` 选项 | 使用自定义请求体 |
|
||
| GET/DELETE 等方法 | 不转发原请求体 |
|
||
| 其他情况 | 深拷贝原请求体 |
|
||
|
||
---
|
||
|
||
## 九、注意事项
|
||
|
||
1. **子请求必须是内部 location**(以 `/` 开头或使用 `internal` 指令)
|
||
2. **嵌套深度有限制**(Nginx 默认 50)
|
||
3. **不能跨 server 块**
|
||
4. **异步操作会挂起父请求** |