Apache 2.4负载均衡反向代理NextJS触发无限重定向故障
我部署了2台NextJS内部应用服务器,由Apache 2.4作为反向代理对外提供服务。两台内部服务地址分别为http://internal:3000/foo和http://internal:3001/foo,对外访问地址为http://external/foo。
直接访问NextJS应用的basePath(即http://internal:3000/foo)时,服务会返回HTTP Code 308状态码重定向到http://internal:3000/foo/bar/baz,预期反向代理场景下实现相同逻辑:访问http://external/foo时自动重定向到http://external/foo/bar/baz。
对应的next.config.js配置如下:
... module.exports = { ... async redirects() { return [ { source: '/', destination: '/bar/baz', permanent: true } ] }, basePath: 'foo' }
未启用负载均衡、仅反向代理单台NextJS应用(例如仅代理到http://internal:3000/foo)时,上述重定向逻辑运行完全正常,使用的配置如下:
<Location "/foo"> ProxyPass "http://localhost:3000/foo" ProxyPassReverse "http://localhost:3000/foo" </Location>
配置反向代理到2台NextJS应用、启用负载均衡时,重定向逻辑完全失效,使用的配置如下:
<Proxy "balancer://example"> BalancerMember "http://localhost:3000/foo" BalancerMember "http://localhost:3001/foo" </Proxy> <Location "/foo"> ProxyPass "balancer://example" ProxyPassReverse "balancer://example" </Location>
此时异常现象为:访问http://external/foo会持续重定向到自身,触发无限重定向循环,最终返回TOO_MANY_REDIRECT错误。单节点反向代理场景重定向正常、启用负载均衡后重定向失效的原因存疑,怀疑启用代理负载均衡时存在未感知到的响应头重写逻辑。
进展1
排查mod_proxy_balancer.c代码,发现其中存在URL重写逻辑调用,相关代码段如下:
access_status = rewrite_url(r, *worker, url); /* Add the session route to request notes if present */ if (route) { apr_table_setn(r->notes, "session-sticky", sticky); apr_table_setn(r->notes, "session-route", route); /* Add session info to env. */ apr_table_setn(r->subprocess_env, "BALANCER_SESSION_STICKY", sticky); apr_table_setn(r->subprocess_env, "BALANCER_SESSION_ROUTE", route); } ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01172) "%s: worker (%s) rewritten to %s", (*balancer)->s->name, (*worker)->s->name, *url); return access_status;
进展2
启用mod_dumpio模块记录Apache进出的全部流量,确认确实存在URL重写行为,具体重写流程如下:
GET /foo:发送到对外Apache服务的原始请求GET /foo/:Apache转发到内部服务器的请求,路径末尾被自动追加斜杠Location /foo:内部服务器返回的重定向响应Location头,符合内部服务逻辑:内部服务收到GET /foo/请求时会重定向到Location /foo,收到GET /foo请求时才会重定向到Location /foo/bar/baz。正常场景下该重定向会由内部服务处理,即GET /foo/触发重定向到GET /foo,最终返回Location /foo/bar/baz,但反向代理场景下该流程未正常执行Location /foo:Apache对外返回的重定向响应Location头,由于该地址与步骤1的原始请求URL完全一致,因此触发了重定向循环。
核心问题出在mod_proxy_balancer的路径规范化逻辑:
- 当BalancerMember配置中携带路径段(比如配置的是
http://localhost:3000/foo)时,模块会自动判定该路径为目录路径,转发请求时自动给末尾无斜杠的匹配路径补全斜杠,把原始请求/foo篡改为/foo/发给后端 - NextJS对
/foo和/foo/的路由处理逻辑完全不同:匹配/foo时会触发配置的根路径重定向到/bar/baz,匹配/foo/时会触发框架自带的目录规范化逻辑重定向回/foo,二者来回跳转形成无限循环 - 单节点ProxyPass配置不会触发balancer模块的路径改写逻辑,转发路径保持原始的
/foo,因此重定向逻辑正常运行
调整Apache负载均衡配置,不要将业务路径/foo配置在BalancerMember中,仅保留后端服务的主机和端口,将路径拼接逻辑统一放到Location段的ProxyPass规则中,避免触发balancer模块的自动路径改写,修改后配置如下:
<Proxy "balancer://example"> # 仅配置后端服务的根地址,不携带业务路径 BalancerMember "http://localhost:3000" BalancerMember "http://localhost:3001" </Proxy> <Location "/foo"> # 转发时统一拼接/foo路径,保证转发给后端的路径和原始请求一致 ProxyPass "balancer://example/foo" ProxyPassReverse "balancer://example/foo" </Location>
如果配置后仍存在斜杠自动补全问题,可以额外添加重写规则强制保留原始请求路径:
RewriteEngine On RewriteRule "^/foo$" "/foo" [PT,L]
修改完成后重启Apache服务即可恢复正常重定向逻辑。
内容的提问来源于stack exchange,提问作者empiskm

