Varnish使用regsuball转大写解决URL百分号编码重定向循环问题
问题根本原因
- 你直接对整个
req.url调用std.tolower,会把百分号编码段里的十六进制大写字符(比如%C3%A4里的A4)转成小写(变成%c3%a4),而部分客户端遵循RFC3986规范会自动将百分号编码的十六进制字符转回大写,导致请求来回跳转形成循环。 - 你尝试的
\U正则修饰符无法生效,是因为Varnish的regsub/regsuball函数仅支持反向引用(\1/\2等),不支持Perl风格的大小写转换修饰符。
解决方案
方案1:解码重编码(最稳妥)
逻辑是先把URL解码为普通字符串,统一转小写后再重新编码,Varnish自带的std.urlencode输出的百分号编码默认就是大写格式,完全符合RFC规范,不会触发客户端自动转换。
import std; sub vcl_recv { # 保存原始URL用于对比 set req.http.x_orig_url = req.url; # 解码→转小写→重新编码,编码后自动生成大写的百分号十六进制 set req.http.x_processed_url = std.urlencode(std.tolower(std.urldecode(req.url))); # 修正编码后开头的/被转成%2F的问题 set req.http.x_processed_url = regsub(req.http.x_processed_url, "^%2F", "/"); # 处理后URL和原始不同才重定向 if (req.http.x_orig_url != req.http.x_processed_url) { return (synth(301, req.http.x_processed_url)); } # 后续正常业务逻辑 } sub vcl_synth { # 处理301重定向响应 if (resp.status == 301) { set resp.http.Location = resp.reason; set resp.status = 301; set resp.reason = "Moved Permanently"; return (deliver); } }
方案2:手动分段处理(无需解码编码)
如果不想做全量解码编码,可以拆分URL的编码段和非编码段分别处理:
import std; sub vcl_recv { set req.http.x_processed_url = req.url; # 第一步:所有非百分号开头的片段转小写 set req.http.x_processed_url = regsuball(req.http.x_processed_url, "([^%]+)", std.tolower("\1")); # 第二步:所有百分号后的十六进制字符统一转大写 set req.http.x_processed_url = regsuball(req.http.x_processed_url, "%([0-9a-f])([0-9a-f])", "%" + std.toupper("\1") + std.toupper("\2")); if (req.url != req.http.x_processed_url) { return (synth(301, req.http.x_processed_url)); } }
如果需要临时重定向,把代码中的301改为302即可。
内容的提问来源于stack exchange,提问作者2ge
相关产品推荐
相关产品推荐

