From a02627738533cede89d5c28862d5a50850a375c2 Mon Sep 17 00:00:00 2001 From: xfy Date: Wed, 15 Apr 2026 17:01:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(config):=20=E6=B7=BB=E5=8A=A0=20redirect?= =?UTF-8?q?=5Frewrite=20=E9=85=8D=E7=BD=AE=E5=AE=9A=E4=B9=89=E5=92=8C?= =?UTF-8?q?=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 RedirectRewriteConfig 配置结构体,支持三种模式: - default: 自动从 target URL 生成改写规则(运行时) - off: 禁用 Location/Refresh 头改写 - custom: 使用预定义规则列表(预编译) - 新增 RedirectRewriteRule 规则结构体,支持正则(~ 前缀)和前缀匹配 - 添加 validateRedirectRewrite 验证函数: - Mode 有效性检查 - custom 模式必须有规则 - 正则表达式预编译检查 - 更新 GenerateConfigYAML 添加配置示例文档 Co-Authored-By: Claude Opus 4.6 --- internal/config/config.go | 61 +++++++++++++++++++++++++++++++++++-- internal/config/defaults.go | 11 +++++++ internal/config/validate.go | 58 +++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 3 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index a70e4f2..02dcc94 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -306,9 +306,10 @@ type ProxyConfig struct { BalancerByLua BalancerByLuaConfig `yaml:"balancer_by_lua"` HealthCheck HealthCheckConfig `yaml:"health_check"` NextUpstream NextUpstreamConfig `yaml:"next_upstream"` - Cache ProxyCacheConfig `yaml:"cache"` - Timeout ProxyTimeout `yaml:"timeout"` - VirtualNodes int `yaml:"virtual_nodes"` + Cache ProxyCacheConfig `yaml:"cache"` + Timeout ProxyTimeout `yaml:"timeout"` + VirtualNodes int `yaml:"virtual_nodes"` + RedirectRewrite *RedirectRewriteConfig `yaml:"redirect_rewrite"` } // BalancerByLuaConfig Lua 负载均衡配置 @@ -475,6 +476,60 @@ type ProxyCacheConfig struct { CacheLock bool `yaml:"cache_lock"` } +// RedirectRewriteConfig Location/Refresh 头改写配置 +// +// 用于配置代理响应中 Location 和 Refresh 头的改写行为。 +// +// 注意事项: +// - Mode 支持 "default"、"off"、"custom" 三种模式 +// - 未配置或空字符串时默认为 "default" 模式 +// - "custom" 模式必须配置至少一条规则 +// +// 使用示例: +// +// redirect_rewrite: +// mode: "default" # 或 "off" 或 "custom" +// rules: +// - pattern: "http://backend:8000/" +// replacement: "$scheme://$host:$server_port/" +type RedirectRewriteConfig struct { + // Mode 运行模式: "default" | "off" | "custom" + // default: 自动从选中的 target URL 生成规则(运行时) + // off: 禁用改写 + // custom: 使用 Rules 列表(预编译) + // 未配置或空字符串时默认为 "default" + Mode string `yaml:"mode"` + + // Rules 改写规则列表,仅在 Mode="custom" 时使用 + Rules []RedirectRewriteRule `yaml:"rules"` +} + +// RedirectRewriteRule 单条改写规则 +// +// 定义 Location/Refresh 头改写的匹配模式和替换目标。 +// +// 注意事项: +// - Pattern 以 ~ 开头表示正则,~* 表示大小写不敏感 +// - 无 ~ 前缀时使用前缀匹配语义 +// - Replacement 支持变量展开($host, $scheme, $server_port 等) +// +// 使用示例: +// +// rules: +// - pattern: "http://backend:8000/" +// replacement: "$scheme://$host:$server_port/" +// - pattern: "~^http://[^/]+:8000/(.*)$" +// replacement: "$scheme://$host/$1" +type RedirectRewriteRule struct { + // Pattern 匹配模式,支持正则(以 ~ 开头)或精确匹配 + // 示例: "http://localhost:8000/" 或 "~^http://[^/]+:8000/" + Pattern string `yaml:"pattern"` + + // Replacement 替换目标,支持变量展开 + // 示例: "$scheme://$host:$server_port/" 或 "/" + Replacement string `yaml:"replacement"` +} + // NextUpstreamConfig 故障转移配置,定义后端失败时的自动重试行为。 // // 当后端返回特定错误状态码或连接失败时,自动尝试下一个可用后端。 diff --git a/internal/config/defaults.go b/internal/config/defaults.go index c523fa3..d28e94d 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -349,6 +349,17 @@ func GenerateConfigYAML(cfg *Config) ([]byte, error) { buf.WriteString(" # script: \"\" # Lua 脚本路径,返回目标索引\n") buf.WriteString(" # fallback: \"round_robin\" # Lua 失败时的备用算法(有效值: round_robin, weighted_round_robin, least_conn)\n") buf.WriteString(" # timeout: 5s # Lua 执行超时\n") + buf.WriteString(" # redirect_rewrite: # Location/Refresh 头改写配置(代理响应重定向时改写 Location 头)\n") + buf.WriteString(" # mode: \"default\" # 运行模式(有效值: default, off, custom)\n") + buf.WriteString(" # # default: 自动从选中的 target URL 生成规则(运行时)\n") + buf.WriteString(" # # off: 禁用改写\n") + buf.WriteString(" # # custom: 使用 rules 列表(预编译)\n") + buf.WriteString(" # rules: [] # 改写规则列表(仅 mode=\"custom\" 时使用)\n") + buf.WriteString(" # # 示例规则(custom 模式):\n") + buf.WriteString(" # # - pattern: \"http://localhost:8000/\" # 匹配模式(无 ~ 前缀为前缀匹配,~ 开头为正则)\n") + buf.WriteString(" # # replacement: \"$scheme://$host:$server_port/\" # 替换目标(支持 $host, $scheme, $server_port 等变量)\n") + buf.WriteString(" # # - pattern: \"~^http://[^/]+:8000/(.*)$\" # 正则匹配示例\n") + buf.WriteString(" # # replacement: \"$scheme://$host/$1\" # 使用捕获组 $1\n") buf.WriteString("\n") // SSL 配置 diff --git a/internal/config/validate.go b/internal/config/validate.go index 017177a..aea5c1c 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "net" + "regexp" "slices" "strings" @@ -488,6 +489,11 @@ func validateProxy(p *ProxyConfig) error { } } + // 验证 redirect_rewrite 配置 + if err := validateRedirectRewrite(p.RedirectRewrite); err != nil { + return fmt.Errorf("redirect_rewrite: %w", err) + } + return nil } @@ -1183,3 +1189,55 @@ func validateLua(l *LuaMiddlewareConfig) error { return nil } + +// validateRedirectRewrite 验证 redirect_rewrite 配置。 +// +// 检查模式有效性、规则完整性和正则表达式编译。 +// +// 参数: +// - cfg: redirect_rewrite 配置对象(nil 时跳过,启用 default 模式) +// +// 返回值: +// - error: 验证失败时返回错误信息,成功返回 nil +// +// 验证规则: +// - Mode 仅允许 ""、"default"、"off"、"custom" +// - custom 模式必须配置至少一条规则 +// - 规则的 pattern 不能为空 +// - 正则模式(~ 前缀)必须能成功编译 +func validateRedirectRewrite(cfg *RedirectRewriteConfig) error { + if cfg == nil { + return nil // 未配置时默认启用 default 模式 + } + + // Mode 验证 + validModes := []string{"", "default", "off", "custom"} + if !slices.Contains(validModes, cfg.Mode) { + return errors.New("redirect_rewrite.mode must be one of: default, off, custom") + } + + // custom 模式必须有规则 + if cfg.Mode == "custom" && len(cfg.Rules) == 0 { + return errors.New("redirect_rewrite.rules required when mode is custom") + } + + // 验证每条规则 + for i, rule := range cfg.Rules { + if rule.Pattern == "" { + return fmt.Errorf("redirect_rewrite.rules[%d].pattern cannot be empty", i) + } + + // 正则模式预编译检查 + if strings.HasPrefix(rule.Pattern, "~") { + patternStr := rule.Pattern[1:] // 去掉 ~ 前缀 + if strings.HasPrefix(rule.Pattern, "~*") { + patternStr = rule.Pattern[2:] // 去掉 ~* 前缀(大小写不敏感) + } + if _, err := regexp.Compile(patternStr); err != nil { + return fmt.Errorf("redirect_rewrite.rules[%d].pattern invalid regex: %v", i, err) + } + } + } + + return nil +}