You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Varnish使用regsuball转大写解决URL百分号编码重定向循环问题

问题根本原因

  1. 你直接对整个req.url调用std.tolower,会把百分号编码段里的十六进制大写字符(比如%C3%A4里的A4)转成小写(变成%c3%a4),而部分客户端遵循RFC3986规范会自动将百分号编码的十六进制字符转回大写,导致请求来回跳转形成循环。
  2. 你尝试的\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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 10:45:05