nginx-源码带读-03-HTTP处理的11个阶段
NGINX 源码带读 第3篇:HTTP处理的11个阶段
本篇目标
理解HTTP请求的完整生命周期,深入分析11个阶段处理流程、phase engine执行机制和checker函数工作原理。
前置知识
- HTTP协议(请求行、请求头、请求体)
- 阅读过第1、2篇
1. 连接到请求的完整流程
TCP连接到达
-> ngx_event_accept() 接受连接
-> ngx_http_init_connection() 初始化HTTP连接
-> ngx_http_wait_request_handler() 等待请求数据
-> ngx_http_init_request() 初始化请求
-> ngx_http_create_request() 创建ngx_http_request_t
-> ngx_http_alloc_request() 分配内存
-> ngx_http_process_request_line() 解析请求行
-> 状态机:method URI HTTP/1.x
-> ngx_http_process_request_headers() 解析请求头
-> 状态机:Key: Value\r\n
-> ngx_http_process_request() 开始处理
-> ngx_http_handler(r) 执行11个阶段
-> ngx_http_core_run_phases() 阶段引擎
-> ngx_http_finalize_request() 完成请求
1.1 请求解析状态机
NGINX使用有限状态机解析HTTP请求:
// src/http/ngx_http_request.c
// 请求行解析状态
typedef enum {
sw_start = 0,
sw_method,
sw_space_before_uri,
sw_schema,
sw_schema_slash,
sw_host_start,
sw_host,
sw_host_end,
sw_host_http,
sw_http_version,
sw_http_09,
sw_space_after_uri,
sw_query_string_start,
sw_query_string,
sw_request_line_end,
} ngx_http_request_state_e;
// 解析流程
void ngx_http_process_request_line(ngx_http_request_t *r) {
for ( ;; ) {
if (r->header_in->pos == r->header_in->last) {
// 需要更多数据
ngx_http_read_request_header(r);
return;
}
// 状态机驱动
rc = ngx_http_parse_request_line(r, r->header_in);
if (rc == NGX_OK) {
// 解析完成,解析请求头
ngx_http_process_request_headers(r);
} else if (rc == NGX_AGAIN) {
// 需要更多数据
return;
} else {
// 解析错误
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
}
}
}
2. ngx_http_request_t
每个HTTP请求对应一个 ngx_http_request_t 结构体(约4235行):
struct ngx_http_request_s {
ngx_uint_t signature; // NGX_HTTP_MODULE
ngx_connection_t *connection; // 底层连接
ngx_http_connection_t *http_connection; // HTTP连接上下文
ngx_pool_t *pool; // 请求级内存池
size_t header_pool_size; // 头部内存池大小
// 请求信息
ngx_str_t request_line; // "GET /path HTTP/1.1"
ngx_str_t uri; // URI路径(未解码)
ngx_str_t args; // 查询参数
ngx_str_t exten; // 文件扩展名
ngx_str_t method_name; // "GET"
ngx_str_t http_protocol; // "HTTP/1.1"
ngx_uint_t method; // 方法枚举(NGX_HTTP_GET等)
ngx_uint_t http_version; // 版本号(1001 = 1.1)
// 头部
ngx_http_headers_in_t headers_in; // 输入头部
ngx_http_headers_out_t headers_out; // 输出头部
ngx_http_chunked_t chunked; // chunk编码状态
// 请求体
ngx_http_request_body_t *request_body; // 请求体
// 阶段处理
ngx_uint_t phase_handler; // 当前阶段处理器索引
ngx_http_handler_t content_handler; // 内容处理函数
ngx_http_handler_t write_event_handler; // 写事件处理函数
// 配置指针(在FIND_CONFIG阶段设置)
void **main_conf; // main级配置
void **srv_conf; // srv级配置
void **loc_conf; // loc级配置
// 上下文
void **ctx; // 各模块的请求上下文
// 子请求
ngx_uint_t subrequests; // 当前子请求数量(最大50)
ngx_http_request_t *main; // 主请求
ngx_http_request_t *parent; // 父请求
// 引用计数
ngx_int_t count; // 引用计数(子请求等)
ngx_http_postponed_request_t *postponed; // 延后处理的子请求
// 状态标志
unsigned header_only:1; // 只需要响应头
unsigned keepalive:1; // 保持连接
unsigned discard_body:1; // 丢弃请求体
unsigned internal:1; // 内部请求
unsigned error_page:1; // 错误页面
// 临时变量
ngx_int_t uri_changes; // URI变更次数(最多10次)
ngx_int_t limit_req_status; // 限流状态码
// 时间戳
time_t start_sec; // 请求开始时间(秒)
ngx_msec_t start_msec; // 请求开始时间(毫秒)
};
2.1 请求分配
// src/http/ngx_http_request.c
static ngx_http_request_t *ngx_http_alloc_request(ngx_connection_t *c) {
ngx_http_request_t *r;
ngx_http_connection_t *hc;
ngx_pool_t *pool;
// 获取server配置中的请求池大小
hc = c->data;
cscf = ngx_http_get_module_srv_conf(hc->conf_ctx[ngx_http_module],
ngx_http_core_module);
pool_size = cscf->request_pool_size; // 默认4096
// 创建请求级内存池
pool = ngx_create_pool(pool_size, c->log);
// 分配请求结构体
r = ngx_pcalloc(pool, sizeof(ngx_http_request_t));
r->pool = pool;
r->connection = c;
// 初始化关键字段
r->signature = NGX_HTTP_MODULE;
r->ctx = ngx_pcalloc(pool, sizeof(void *) * ngx_http_max_module);
// 复制配置指针(快速路径,避免每次查找)
r->main_conf = hc->conf_ctx[ngx_http_module]->main_conf;
r->srv_conf = hc->conf_ctx[ngx_http_module]->srv_conf;
r->loc_conf = hc->conf_ctx[ngx_http_module]->loc_conf;
// 初始化默认值
r->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1; // 10+1
r->subrequests = NGX_HTTP_MAX_SUBREQUESTS + 1; // 50+1
r->count = 1;
// 设置时间戳(使用缓存时间,避免系统调用)
tp = ngx_timeofday();
r->start_sec = tp->sec;
r->start_msec = tp->msec;
// 初始化读事件处理
r->read_event_handler = ngx_http_block_reading;
return r;
}
2.2 引用计数
NGINX使用引用计数管理请求生命周期:
// 主请求 count = 1
// 子请求 count = 主请求.count + 1
// 读取请求体 count++
void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc) {
// 如果有子请求在进行
if (r != r->main) {
r->main->count--;
if (r->main->count == 0) {
// 所有子请求完成,释放主请求
ngx_http_close_request(r->main);
}
return;
}
// ...
}
3. 11个处理阶段
3.1 阶段概览
// src/http/ngx_http_core_module.h
typedef enum {
NGX_HTTP_POST_READ_PHASE = 0, // 读完请求头后
NGX_HTTP_SERVER_REWRITE_PHASE, // 服务器级别重写
NGX_HTTP_FIND_CONFIG_PHASE, // 查找location配置(内部)
NGX_HTTP_REWRITE_PHASE, // location级别重写
NGX_HTTP_POST_REWRITE_PHASE, // 重写后处理
NGX_HTTP_PREACCESS_PHASE, // 访问控制前
NGX_HTTP_ACCESS_PHASE, // 访问控制
NGX_HTTP_POST_ACCESS_PHASE, // 访问控制后
NGX_HTTP_PRECONTENT_PHASE, // 内容处理前
NGX_HTTP_CONTENT_PHASE, // 内容处理
NGX_HTTP_LOG_PHASE // 日志记录
} ngx_http_phases_e;
3.2 阶段handler数组
每个阶段有独立的handler数组,通过配置积累:
typedef struct {
ngx_array_t handlers; // 该阶段的handler数组
} ngx_http_phase_t;
// ngx_http_conf_ctx_t中:
typedef struct {
ngx_array_t *handlers[NGX_HTTP_LOG_PHASE + 1]; // 11个阶段
} ngx_http_phase_engine_t;
3.3 各阶段详解
阶段0 POST_READ:读完所有请求头后执行
- realip模块在此阶段工作(获取真实客户端IP)
- 检查请求体大小限制
阶段1 SERVER_REWRITE:服务器级别的重写
- server块中的rewrite指令在此阶段执行
- 在location匹配之前执行
阶段2 FIND_CONFIG:查找location配置(NGINX内部)
- 根据URI匹配location块
- 用户不可配置此阶段
- 匹配结果决定后续阶段使用哪个loc_conf
阶段3 REWRITE:location级别的重写
- location块中的rewrite指令在此阶段执行
- 可以返回重定向(301/302/307/308)
- 可以设置内部跳转(break/last)
阶段4 POST_REWRITE:重写后的处理
- 检查重写是否导致内部跳转
- 如果URI改变,跳回FIND_CONFIG阶段重新匹配
- 检查重写次数限制(最多10次)
阶段5 PREACCESS:访问控制前
- limit_req(限流)和limit_conn(限连接数)在此阶段
- limit_req检查请求速率
- limit_conn检查并发连接数
阶段6 ACCESS:访问控制
- allow/deny指令
- auth_basic(HTTP基本认证)在此阶段
- satisfy any/all逻辑
阶段7 POST_ACCESS:访问控制后
- 如果被拒绝,在此阶段返回403
- 处理satisfy any/all的结果
阶段8 PRECONTENT:内容处理前
- mirror模块在此阶段(镜像流量)
- try_files指令在此阶段(文件存在性检查)
- 内部跳转
阶段9 CONTENT:内容处理
- 这是最重要的阶段!
- proxy_pass、fastcgi_pass、static文件服务在此阶段
- 每个location只能有一个content handler
阶段10 LOG_PHASE:日志记录
- access_log在此阶段写入
- 请求结束时执行(在free_request中调用)
3.4 阶段处理器返回值
| 返回值 | 含义 | 后续动作 |
|---|---|---|
NGX_OK |
继续下一阶段 | checker推进phase_handler |
NGX_DECLINED |
跳过当前阶段的后续处理器 | 继续下一个handler |
NGX_AGAIN |
等待更多数据 | 暂停阶段引擎 |
NGX_DONE |
请求已完成 | 停止阶段引擎 |
NGX_ERROR |
发生错误 | 终结请求 |
NGX_HTTP_OK (200) |
请求成功 | 终结请求 |
NGX_HTTP_NOT_FOUND (404) |
未找到 | 返回404响应 |
3.5 阶段执行流程图
POST_READ --> SERVER_REWRITE --> FIND_CONFIG --> REWRITE --> POST_REWRITE
| | | | |
v v v v v
realip rewrite find_config rewrite post_rewrite
handler handler handler handler handler
| | | | |
+--------------+------------------+-------------+-------------+
|
v
PREACCESS --> ACCESS --> POST_ACCESS --> PRECONTENT --> CONTENT --> LOG
| | | | | |
v v v v v v
limit_req allow/deny post_access try_files proxy_pass access_log
limit_conn auth_basic handler mirror fastcgi write_log
4. ngx_http_core_run_phases —— 阶段引擎
// src/http/ngx_http_core_module.c
void ngx_http_core_run_phases(ngx_http_request_t *r) {
ngx_int_t rc;
ngx_http_phase_handler_t *ph;
ngx_http_phase_engine_t *engine;
engine = r->phase_engine;
// 从当前phase_handler开始执行
ph = engine->handlers;
rc = NGX_OK;
// 循环执行checker,直到checker返回NGX_OK或没有checker
while (ph[r->phase_handler].checker) {
rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
if (rc == NGX_OK) {
break; // 请求已完成或暂停
}
}
}
4.1 phase_handler结构
typedef struct ngx_http_phase_handler_s {
ngx_http_phase_handler_pt checker; // 检查器函数
ngx_http_handler_pt handler; // 实际处理函数
ngx_uint_t next; // 下一个phase_handler索引
} ngx_http_phase_handler_t;
4.2 checker函数
每个阶段有专门的checker函数,决定如何推进phase_engine:
通用checker(用于POST_READ、PREACCESS等):
static ngx_int_t ngx_http_core_generic_phase(ngx_http_request_t *r,
ngx_http_phase_handler_t *ph) {
ngx_int_t rc;
// 调用handler
rc = ph->handler(r);
if (rc == NGX_OK) {
// 继续下一个phase_handler
r->phase_handler = ph->next;
return NGX_AGAIN;
}
if (rc == NGX_DECLINED) {
// 继续当前阶段的下一个handler
r->phase_handler++;
return NGX_AGAIN;
}
if (rc == NGX_AGAIN || rc == NGX_DONE) {
// 暂停阶段引擎
return NGX_OK;
}
// 其他返回值:终结请求
r->phase_handler = ph->next;
return NGX_AGAIN;
}
rewrite阶段checker:
static ngx_int_t ngx_http_core_rewrite_phase(ngx_http_request_t *r,
ngx_http_phase_handler_t *ph) {
ngx_int_t rc;
rc = ph->handler(r);
if (rc == NGX_DECLINED) {
r->phase_handler++;
return NGX_AGAIN;
}
if (rc == NGX_DONE || rc == NGX_OK || rc == NGX_HTTP_SET_URI) {
// 重写完成,跳到FIND_CONFIG重新匹配
if (ph->next) {
r->phase_handler = ph->next;
return NGX_AGAIN;
}
return NGX_OK;
}
// 其他返回值:终结请求
ngx_http_finalize_request(r, rc);
return NGX_OK;
}
access阶段checker:
static ngx_int_t ngx_http_core_access_phase(ngx_http_request_t *r,
ngx_http_phase_handler_t *ph) {
ngx_int_t rc;
// 子请求跳过access阶段
if (r != r->main) {
r->phase_handler = ph->next;
return NGX_AGAIN;
}
// 检查是否有content_handler(内部跳转)
if (r->content_handler) {
r->phase_handler = ph->next;
return NGX_AGAIN;
}
rc = ph->handler(r);
if (rc == NGX_DECLINED) {
r->phase_handler++;
return NGX_AGAIN;
}
if (rc == NGX_AGAIN || rc == NGX_DONE) {
return NGX_OK;
}
// 访问控制结果
if (rc == NGX_HTTP_FORBIDDEN || rc == NGX_HTTP_UNAUTHORIZED) {
// 设置access_code,在POST_ACCESS阶段处理
r->access_code = rc;
r->phase_handler = ph->next;
return NGX_AGAIN;
}
// 其他返回值
r->phase_handler = ph->next;
return NGX_AGAIN;
}
content阶段checker:
static ngx_int_t ngx_http_core_content_phase(ngx_http_request_t *r,
ngx_http_phase_handler_t *ph) {
// 如果有content_handler(proxy_pass等设置的)
if (r->content_handler) {
// 直接调用content_handler
r->content_handler(r);
return NGX_OK;
}
// 否则使用阶段handler
rc = ph->handler(r);
if (rc == NGX_DECLINED) {
// 继续下一个content handler
r->phase_handler++;
return NGX_AGAIN;
}
// 其他返回值:终结请求
ngx_http_finalize_request(r, rc);
return NGX_OK;
}
4.3 阶段引擎执行流程
ngx_http_core_run_phases()
|
+-> while (ph[phase_handler].checker)
|
+-> checker(r, ph) --+
^ |
| NGX_AGAIN <------+
| (继续循环)
|
+-> NGX_OK (停止)
|
+-> 请求完成或暂停
5. 阶段编译
5.1 ngx_http_init_phase_handlers()
static ngx_int_t ngx_http_init_phase_handlers(ngx_conf_t *cf,
ngx_http_phase_engine_t *engine) {
ngx_http_phase_t *ph;
ngx_http_phase_handler_t *handlers;
ngx_http_phase_handler_t *last;
ngx_http_handler_pt *log;
// 计算总handler数
n = 1; // find_config阶段必须有handler
for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) {
n += ph[i].handlers.nelts;
}
// 分配handler数组
handlers = ngx_pcalloc(cf->pool, n * sizeof(ngx_http_phase_handler_t));
// 填充handler
for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) {
if (ph[i].handlers.nelts == 0) {
continue;
}
// 逆序填充(后添加的handler先执行)
for (j = ph[i].handlers.nelts - 1; j >= 0; j--) {
handler = ph[i].handlers.elts;
handlers->handler = handler[j];
// 设置checker
switch (i) {
case NGX_HTTP_POST_READ_PHASE:
handlers->checker = ngx_http_core_generic_phase;
break;
case NGX_HTTP_SERVER_REWRITE_PHASE:
handlers->checker = ngx_http_core_rewrite_phase;
break;
case NGX_HTTP_FIND_CONFIG_PHASE:
handlers->checker = ngx_http_core_find_config_phase;
break;
// ... 其他阶段
}
// 设置next指针
if (i < NGX_HTTP_FIND_CONFIG_PHASE) {
handlers->next = i + 1;
} else if (i == NGX_HTTP_FIND_CONFIG_PHASE) {
handlers->next = i + 2; // 跳过find_config
} else {
handlers->next = i + 1;
}
handlers++;
}
}
// 设置阶段引擎
engine->handlers = handlers;
}
6. 一个典型的请求处理示例
server {
listen 80;
server_name example.com;
location /api {
rewrite ^/api/(.*)$ /$1 break; # REWRITE阶段
proxy_pass http://backend; # CONTENT阶段
}
location /static {
root /var/www; # CONTENT阶段(静态文件)
}
}
请求 GET /api/users 的处理:
POST_READ
-> realip模块获取真实IP
-> 检查请求头完整性
SERVER_REWRITE
-> 无server级rewrite
FIND_CONFIG
-> 匹配location /api
-> 设置r->loc_conf = /api的配置
REWRITE
-> rewrite ^/api/(.*)$ /$1 break
-> URI从 /api/users 变为 /users
-> break标志:不继续重写
POST_REWRITE
-> URI已改变,跳回FIND_CONFIG重新匹配
FIND_CONFIG(第二次)
-> 匹配location /api(重写后的URI仍匹配)
PREACCESS
-> limit_req检查
-> limit_conn检查
ACCESS
-> 检查allow/deny
-> 检查auth_basic
POST_ACCESS
-> 无(访问控制通过)
PRECONTENT
-> try_files检查
CONTENT
-> proxy_pass http://backend
-> 代理到后端服务器
-> 接收响应
-> 发送响应
LOG
-> 写入access_log
7. 子请求(Subrequest)
NGINX支持将一个请求分解为多个子请求:
主请求 GET /page
-> 子请求1 GET /header (SSI include)
-> 子请求2 GET /content
-> 子请求3 GET /footer
7.1 子请求创建
// src/http/ngx_http_request.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 *handler, ngx_uint_t flags) {
ngx_http_request_t *sr;
// 创建子请求
sr = ngx_pcalloc(r->pool, sizeof(ngx_http_request_t));
// 继承主请求的配置
sr->main_conf = r->main_conf;
sr->srv_conf = r->srv_conf;
sr->loc_conf = r->loc_conf;
// 设置子请求URI
sr->uri = *uri;
sr->args = args ? *args : ngx_null_string;
// 子请求共享主请求的连接
sr->connection = r->connection;
// 增加引用计数
sr->count = 1;
r->count++;
// 设置子请求处理器
if (handler) {
sr->post_subrequest = handler;
}
// 注册到主请求的子请求链表
if (r->main->subrequests == 0) {
// 超过子请求限制(50个)
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"subrequests cycle");
return NGX_ERROR;
}
r->main->subrequests--;
*psr = sr;
return NGX_OK;
}
7.2 子请求的限制
- 最大50个子请求(
NGX_HTTP_MAX_SUBREQUESTS 50) - 子请求不能修改主请求的URI(除非使用内部跳转)
- 子请求共享主请求的连接和内存池
- 子请求的响应通过过滤链合并到主请求
7.3 子请求vs直接HTTP请求
| 特性 | 子请求 | 直接HTTP请求 |
|---|---|---|
| 创建方式 | ngx_http_subrequest() |
ngx_http_upstream_init() |
| 连接 | 共享主请求的连接 | 独立的TCP连接 |
| 内存池 | 共享主请求的pool | 独立的pool |
| 配置 | 使用主请求的配置 | 使用独立的配置 |
| 响应 | 合并到主请求 | 独立响应 |
| 性能 | 高效(无TCP开销) | 较低(需要TCP三次握手) |
| 场景 | SSI、模板包含 | 外部API调用 |
8. 本篇小结
| 概念 | 要点 |
|---|---|
| 请求流程 | accept -> 解析行 -> 解析头 -> 11阶段 -> finalize |
| 11阶段 | POST_READ到LOG_PHASE,每阶段有checker+handler |
| CONTENT阶段 | 最重要,proxy_pass/fastcgi/static在此 |
| checker | 决定阶段如何推进(generic/rewrite/access/content) |
| 子请求 | SSI等场景使用,最大50个 |
| 内存池 | 每个请求一个pool,请求结束自动释放 |
| 引用计数 | count管理请求生命周期 |
思考题
- 为什么 FIND_CONFIG 阶段不可配置?这对架构有什么好处?
- limit_req 在 PREACCESS 而非 ACCESS 阶段,为什么?
- 如果一个请求需要同时代理到3个后端服务,如何实现?
- NGINX的子请求和直接发起新HTTP请求有什么区别?
思考题解答
1. 为什么FIND_CONFIG阶段不可配置?
原因:
FIND_CONFIG阶段的核心任务是将URI匹配到对应的location配置块。这是NGINX配置系统的基础操作,不能被用户代码干扰。
架构好处:
-
确定性:location匹配的结果决定了后续所有阶段使用哪个配置,必须在阶段2完成,不能被模块延迟或修改。
-
隔离性:用户代码(rewrite规则等)不能破坏配置查找逻辑。如果允许自定义handler,可能导致配置系统混乱。
-
性能优化:NGINX可以在此阶段预计算匹配结果,后续阶段直接使用,避免重复匹配。
-
内部跳转支持:rewrite指令可能改变URI,导致需要重新匹配location。FIND_CONFIG阶段处理这种内部跳转。
实现:
// src/http/ngx_http_core_module.c
static ngx_int_t ngx_http_core_find_config_phase(ngx_http_request_t *r,
ngx_http_phase_handler_t *ph) {
// 查找匹配的location
ngx_http_core_find_location(r);
// 设置r->loc_conf
r->loc_conf = r->loc_conf;
// 检查client_max_body_size
if (r->headers_in.content_length_n > clcf->client_max_body_size) {
ngx_http_finalize_request(r, NGX_HTTP_REQUEST_ENTITY_TOO_LARGE);
return NGX_OK;
}
// 处理内部跳转
if (rc == NGX_DONE) {
// 返回301重定向
ngx_http_finalize_request(r, NGX_HTTP_MOVED_PERMANENTLY);
return NGX_OK;
}
// 继续下一阶段
r->phase_handler++;
return NGX_AGAIN;
}
2. limit_req在PREACCESS而非ACCESS阶段,为什么?
原因:限流应该在访问控制之前执行,因为:
-
先限流后认证:如果先做认证再限流,攻击者可以通过大量无效认证请求消耗服务器资源。限流应该在最前面,拒绝超过速率的请求。
-
模块职责分离:
- PREACCESS:限流、限连接数(基于速率的控制)
- ACCESS:认证、授权(基于身份的控制)
-
避免无效计算:如果请求被限流拒绝,就不需要执行后续的认证、授权等操作,节省CPU。
-
配置语义:
limit_req_zone和limit_req指令在PREACCESS阶段工作,与allow/deny的ACCESS阶段逻辑清晰分离。
典型流程:
请求到达
-> PREACCESS:limit_req检查 → 503 返回
-> ACCESS:allow/deny检查 → 403 返回
-> CONTENT:正常处理
3. 如何同时代理到3个后端服务?
方法1:子请求(Subrequest):
// 在content handler中创建子请求
ngx_http_subrequest(r, &uri, NULL, &sr, NULL, flags);
方法2:使用mirror模块:
location /api {
mirror /mirror1;
mirror /mirror2;
proxy_pass http://backend3;
}
location /mirror1 {
proxy_pass http://backend1;
}
location /mirror2 {
proxy_pass http://backend2;
}
方法3:使用upstream和lua:
location /api {
content_by_lua_block {
local http = require "resty.http"
-- 并发请求3个后端
local res1, res2, res3 =
http.request("http://backend1/api"),
http.request("http://backend2/api"),
http.request("http://backend3/api")
-- 合并结果
}
}
方法4:使用SSI(Server Side Includes):
location /page {
ssi on;
# 页面中包含多个子请求
}
推荐方案:对于同步场景使用子请求,对于异步并发场景使用lua协程。
4. 子请求和直接HTTP请求的区别?
| 特性 | 子请求 | 直接HTTP请求 |
|---|---|---|
| 创建方式 | ngx_http_subrequest() |
ngx_http_upstream_init() |
| 连接 | 共享主请求的连接 | 独立的TCP连接 |
| 内存池 | 共享主请求的pool | 独立的pool |
| 配置 | 使用主请求的配置 | 使用独立的配置 |
| 响应 | 合并到主请求 | 独立响应 |
| 性能 | 高效(无TCP开销) | 较低(需要TCP三次握手) |
| 场景 | SSI、模板包含 | 外部API调用 |
子请求的优势:
- 不需要建立新的TCP连接
- 不需要DNS解析
- 可以共享主请求的连接池和缓存
- 响应直接写入主请求的过滤链
子请求的限制:
- 不能跨域(必须在同一server内)
- 不能修改主请求的URI(除非使用内部跳转)
- 子请求的响应格式必须与主请求兼容
实际应用:
- SSI(Server Side Includes):在HTML中包含其他URL的内容
- 模板系统:将页面拆分为多个子请求
- API聚合:将多个内部API调用合并为一个外部请求

浙公网安备 33010602011771号