nginx通过11个预定义phase构成可插拔执行骨架,模块按需注入handler链表、由配置指令驱动启用、仅提供标准签名函数,实现真正插件化;第三方模块可无缝接入同一phase体系。

Nginx 的多阶段处理架构通过“phase handler 链 + 统一注册机制 + 配置驱动”三者协同,实现模块真正意义上的插件化——模块不硬编码进流程,而是按需注入、彼此隔离、运行时生效。
11 个预定义 phase 构成可插拔的执行骨架
HTTP 请求不是靠 if-else 分发,而是被强制纳入一套固定顺序的 11 个处理阶段(如 NGX_HTTP_POST_READ_PHASE、NGX_HTTP_CONTENT_PHASE、NGX_HTTP_LOG_PHASE 等)。每个 phase 对应一个 handler 链表,结构为单向链表节点数组。
- 模块不决定“自己在哪跑”,只声明“想在哪个 phase 插入”;
- 插入动作发生在模块的 postconfiguration 回调中,由核心统一调用;
- 例如 rewrite 模块在 postconfiguration 中将
ngx_http_rewrite_handler推入NGX_HTTP_SERVER_REWRITE_PHASE链表末尾; - 多个模块可共存于同一 phase,执行顺序由插入先后决定,无需修改其他模块代码。
模块仅提供 handler 函数指针,不参与调度逻辑
每个 phase handler 是一个函数指针,签名统一为 ngx_int_t (*handler)(ngx_http_request_t *r)。模块只需实现该函数,返回 NGX_OK(继续)、NGX_DECLINED(交由下一 handler)、NGX_DONE(终止当前 phase)等标准码。
- handler 内部不调用其他模块,也不感知上下游模块是否存在;
- 核心在执行某 phase 时,遍历其 handler 链表,逐个调用,根据返回值决定是否中断或跳转;
- 比如 content phase 中,
ngx_http_static_handler和ngx_http_proxy_handler可同时注册,但实际只由 location 匹配结果决定最终走哪一个——这是配置层控制,非代码层耦合。
配置指令驱动模块启用与参数绑定
模块是否生效、在哪些 location 生效、使用什么参数,全部由配置文件中的指令触发,而非编译期或启动期静态绑定。
- 模块通过
commands数组声明支持的指令(如rewrite、proxy_pass、add_header); -
nginx 启动时扫描配置,匹配到对应指令后,调用该模块的 set 回调解析参数,并写入模块专属配置结构体(由
create_conf分配); - 同一个模块的 handler 在不同 server 或 location 块中可能持有不同配置,靠
merge_conf实现父子继承与覆盖; - 没有配置指令调用,模块的 handler 就不会被插入任何 phase 链表——即“存在但不激活”。
第三方模块可无缝接入同一套 phase 体系
只要遵循 ngx_module_t 结构体规范,实现正确的 postconfiguration 和 handler 函数,并在配置中提供对应指令,第三方模块就能自然融入原生 phase 流程。
- 例如
echo-nginx-module提供echo指令,在 postconfiguration 中将 echo handler 插入NGX_HTTP_CONTENT_PHASE; - 又如
lua-nginx-module提供content_by_lua_block,同样注册到 content phase,但内部执行 Lua 脚本; - 它们与
ngx_http_proxy_module共享 phase 链表结构、返回值语义和错误传播机制,用户切换只需改配置,无需改架构。











