Nginx Handler模块必须将ngx_http_handler_pt函数赋给ngx_http_module_t的handler字段,并在匹配的location中通过自定义指令(如hello_world on)显式绑定该handler,否则因phase未注册或location未匹配导致不触发;响应体需用ngx_create_temp_buf分配、显式设置content_length_n,并严格按ngx_http_send_header→ngx_http_output_filter顺序发送。

能直接用 ngx_http_handler_pt 写出可运行的 Nginx Handler 模块,但必须严格对齐 Nginx 请求阶段生命周期,否则 handler 不会被调用、返回空白或 500。
handler 函数为什么没被触发?location 匹配和 phase 注册缺一不可
很多人写了 ngx_http_handler_pt 函数,也注册了指令,但访问 /hello 仍是 404 或走默认静态文件逻辑——根本原因是 handler 没挂到 content phase,或者 location 根本没匹配上你的模块。
- 必须把 handler 函数赋给模块结构体中
ngx_http_module_t的handler字段(不是postconfiguration或其他回调) -
location /hello { hello_world on; }中的hello_world指令,必须在ngx_http_mytest解析函数里显式设置clcf->handler = ngx_http_hello_handler,不能只存配置开关 - 如果用了
NGX_HTTP_LMT_CONF权限注册指令,却在location块外写hello_world on,Nginx 启动时会报"hello_world" directive is not allowed here
响应体写入失败的三个常见原因
即使 handler 被调用,也常出现响应为空、Content-Length 错误、或连接被 reset——多半卡在响应体构造环节。
-
r->headers_out.content_length_n必须显式赋值为响应字符串长度,不能依赖自动计算;漏设会导致 Nginx 不发 body 或发空包 - 缓冲区要用
ngx_create_temp_buf(r->pool, len)分配,不能用栈变量或静态字符串直接 memcpy;否则内存可能提前释放 - 必须按顺序调用:
ngx_http_send_header(r)→ngx_http_output_filter(r, &out);少一步或顺序颠倒,body 就不会发出
编译时链接失败:找不到 ngx_http_core_module 等符号
自定义模块源码里用了 ngx_http_conf_get_module_loc_conf、ngx_http_send_header 等函数,但 make 报 undefined reference——说明没正确链接 Nginx 内部模块依赖。
- 确保
./configure时用了完整路径:--add-module=/home/nginx/mytest,路径末尾不能有斜杠 - 模块源码中必须包含
#include <ngx_http.h></ngx_http.h>,且该头文件来自你正在编译的 Nginx 源码树(不是系统安装的 nginx-devel) - 不要手动改
objs/Makefile加 -lnginx;Nginx 构建系统会自动处理依赖,硬加反而破坏符号解析顺序
调试 handler 是否生效最有效的办法
别依赖日志级别或浏览器响应——Nginx handler 执行极快,日志可能来不及刷,而 HTTP 层错误又太笼统。
- 在 handler 开头加
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "hello_handler invoked");,然后tail -f /usr/local/nginx/logs/error.log实时看 - 用
curl -v http://localhost/hello看完整响应头,确认Content-Length和Content-Type是否符合预期 - 最关键的验证:把 handler 最后一行改成
return NGX_HTTP_INTERNAL_SERVER_ERROR;,如果返回 500,说明 handler 确实执行了;如果还是 404,说明根本没进这个函数
真正容易被忽略的是:handler 函数一旦返回 NGX_OK 或任意 NGX_HTTP_* 状态码,Nginx 就认为该请求已终结,后续所有 content phase 模块(包括你自己的其他 handler、甚至 rewrite 模块)都不会再运行。这意味着它不是“插件”,而是“接管者”——设计时必须想清楚是否要完全替代默认行为。











