Nginx 1.24.0动态模块Handler回调被调用两次导致IP提前封禁问题
嘿,我之前做Nginx模块开发时也踩过一模一样的坑,太懂那种明明设置了20次限制结果10次就被封的憋屈了!咱们来好好捋捋为什么你的handler会被调用两次:
最容易踩的几个坑:
- 子请求触发
Nginx处理某些请求时会自动发起内部子请求,比如开了autoindex on访问目录、用了SSI、或者rewrite模块触发的内部重定向,都会产生子请求。要是你的模块挂载到了NGX_HTTP_CONTENT_PHASE这类会被子请求执行的阶段,主请求和子请求都会触发handler,相当于一次外部请求被计数两次。
你可以在handler里加个判断,跳过子请求的统计:
if (r->internal) { return NGX_DECLINED; // 子请求直接跳过计数逻辑 }
这样就只会统计真实的外部请求啦。
- 模块挂载阶段选错
要是你把handler注册到了NGX_HTTP_PRECONTENT_PHASE或者更早的阶段,可能会因为Nginx请求流程里的某些操作(比如rewrite重定向、URI修正)导致该阶段被重复执行。比如rewrite修改了请求URI后,Nginx会重新走一遍部分处理阶段,你的handler自然就被调用第二次了。
对于IP访问计数这类需求,更推荐挂载到NGX_HTTP_ACCESS_PHASE——这个阶段是Nginx专门用来做访问控制的,只会执行一次,而且在内容处理前就能拦截违规请求,逻辑更合理。
配置重复注册handler
要是你在模块的配置解析逻辑里,不小心把handler注册了两次(比如同时在http块和server块都注册了同一个handler实例),那每个请求就会被两个handler各处理一次,计数直接翻倍。可以检查下你的ngx_http_module_t结构体里的配置钩子,看看是不是重复添加了handler注册的逻辑。HEAD请求的特殊情况
这种情况相对少见,但Nginx处理HEAD请求时,会先像GET请求一样执行handler,再去掉响应体返回。不过一般Nginx会保证handler只调用一次,但如果你的代码里对HEAD请求做了特殊分支处理,可能会不小心触发重复计数,也可以排查下这块逻辑。
快速排查小妙招
你可以在handler里加一行调试日志,打印请求的URI和是否为内部子请求的标记:
ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "Handler triggered: uri=%V, internal=%d", &r->uri, r->internal);
然后打开Nginx的debug日志(编译时要加--with-debug参数,配置文件里设置error_log logs/error.log debug;),看看每次触发的请求详情,就能快速定位到底是子请求还是阶段重复执行的问题啦!
备注:内容来源于stack exchange,提问作者tyxWilliam

