ngx_http_core_run_phases 是带跳转控制的阶段状态机引擎,依赖 checker 函数和 handler 返回值(如 NGX_OK、NGX_DECLINED、NGX_AGAIN)驱动流程,仅 CONTENT_PHASE 允许输出响应体;11 个阶段中仅 6 个开放注册,其余由核心独占。

直接说结论:ngx_http_core_run_phases 不是线性调用 11 个函数,而是一个带跳转控制的阶段状态机引擎——它靠 checker 函数决定是否进入下一阶段,靠 handler 返回值(如 NGX_OK、NGX_DECLINED、NGX_AGAIN)决定流程走向,且只有 NGX_HTTP_CONTENT_PHASE 阶段允许真正输出响应体。
为什么 ngx_http_core_run_phases 不会“顺序执行完全部 11 个阶段”
因为多数阶段在无 handler 或 handler 返回 NGX_DECLINED 时会被快速跳过;而一旦某个 handler 返回 NGX_OK,就立即进入下一阶段,不等本阶段其余 handler;若返回 NGX_AGAIN 或 NGX_DONE,则整个流程暂停,等待事件就绪后从当前阶段重入,不会推进到下一阶段。
常见错误现象包括:
- 自定义模块注册到了
NGX_HTTP_FIND_CONFIG_PHASE却始终不被调用——该阶段由ngx_http_core_module独占,禁止第三方注册 - 在
NGX_HTTP_ACCESS_PHASE中调用ngx_http_send_header()导致 core dump——只有CONTENT_PHASE允许构造完整响应 - rewrite 后反复卡在
NGX_HTTP_POST_REWRITE_PHASE并返回 500——说明 rewrite 次数超限,触发了死循环保护
哪些阶段能挂 handler?哪些根本不能动
不是所有阶段都开放给模块注册 handler。Nginx 编译期硬编码了可挂载范围,实际只允许在以下 6 个阶段添加自定义 handler:
NGX_HTTP_POST_READ_PHASENGX_HTTP_SERVER_REWRITE_PHASENGX_HTTP_REWRITE_PHASENGX_HTTP_PREACCESS_PHASENGX_HTTP_ACCESS_PHASENGX_HTTP_CONTENT_PHASE
其余 5 个阶段(NGX_HTTP_FIND_CONFIG_PHASE、NGX_HTTP_POST_REWRITE_PHASE、NGX_HTTP_POST_ACCESS_PHASE、NGX_HTTP_TRY_FILES_PHASE、NGX_HTTP_LOG_PHASE)要么由核心模块独占实现,要么仅作调度收尾之用,强行注册会导致配置加载失败或运行时未定义行为。
checker 和 handler 是怎么配对并驱动跳转的
每个阶段不直接存 handler 列表,而是通过 ngx_http_phase_engine_t 中的 handlers 数组统一管理,每个元素是 struct ngx_http_phase_handler_s,含三要素:
-
checker:阶段级调度逻辑,比如判断是否要跳回REWRITE_PHASE(遇到内部重定向)或跳过空阶段 -
handler:模块注册的实际处理函数 -
next:指向下一个阶段第一个 handler 在数组中的索引,用于快速跳转,避免遍历全部
例如,当 NGX_HTTP_ACCESS_PHASE 中某个 handler 返回 NGX_HTTP_FORBIDDEN,它的 checker 会主动将流程导向 NGX_HTTP_POST_ACCESS_PHASE,而不是继续遍历本阶段剩余 handler 或进入 CONTENT_PHASE。
自定义模块该往哪个阶段注册 handler 才安全有效
推荐优先考虑三个“高自由度 + 低冲突”阶段:
-
NGX_HTTP_PREACCESS_PHASE:适合做请求预检(如 UA 过滤、IP 标记),不影响鉴权逻辑 -
NGX_HTTP_ACCESS_PHASE:标准鉴权入口,返回NGX_HTTP_UNAUTHORIZED会自动交由POST_ACCESS_PHASE发送 401 响应 -
NGX_HTTP_CONTENT_PHASE:唯一能调用ngx_http_send_header()和ngx_http_output_filter()的阶段,适合生成响应体(静态服务、反代、Lua)
别碰 NGX_HTTP_FIND_CONFIG_PHASE ——它在 location 匹配前运行,但匹配逻辑本身就在该阶段内完成,外部 handler 插入进去只会破坏路由一致性;也别在 LOG_PHASE 尝试修改响应头,此时连接可能已关闭,写操作会 segfault。
最易被忽略的一点:阶段跳转不是靠 return 值“通知引擎”,而是由 checker 函数根据当前 handler 返回值 + 当前请求上下文(如 r->uri_changed 是否为 1)共同决策;所以即使你写了 return NGX_OK,如果 checker 判定要重进 rewrite,流程照样绕回 REWRITE_PHASE。











