feat(config): 添加 redirect_rewrite 配置定义和验证

- 新增 RedirectRewriteConfig 配置结构体,支持三种模式:
  - default: 自动从 target URL 生成改写规则(运行时)
  - off: 禁用 Location/Refresh 头改写
  - custom: 使用预定义规则列表(预编译)
- 新增 RedirectRewriteRule 规则结构体,支持正则(~ 前缀)和前缀匹配
- 添加 validateRedirectRewrite 验证函数:
  - Mode 有效性检查
  - custom 模式必须有规则
  - 正则表达式预编译检查
- 更新 GenerateConfigYAML 添加配置示例文档

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
xfy 2026-04-15 17:01:07 +08:00
parent aa73df964e
commit a026277385
3 changed files with 127 additions and 3 deletions

View File

@ -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 故障转移配置,定义后端失败时的自动重试行为。
//
// 当后端返回特定错误状态码或连接失败时,自动尝试下一个可用后端。

View File

@ -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 配置

View File

@ -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
}