添加302跳转支持,更新相关配置和处理逻辑

This commit is contained in:
wood chen 2025-05-27 08:18:40 +08:00
parent 6bdcaf6f83
commit 1a2c7bd06d
4 changed files with 178 additions and 13 deletions

View File

@ -13,6 +13,7 @@ type PathConfig struct {
DefaultTarget string `json:"DefaultTarget"` // 默认目标URL
ExtensionMap []ExtRuleConfig `json:"ExtensionMap"` // 扩展名映射规则
ExtRules []ExtensionRule `json:"-"` // 内部使用,存储处理后的扩展名规则
RedirectMode bool `json:"RedirectMode"` // 是否使用302跳转模式
}
// ExtensionRule 表示一个扩展名映射规则(内部使用)
@ -21,6 +22,7 @@ type ExtensionRule struct {
Target string // 目标服务器
SizeThreshold int64 // 最小阈值
MaxSize int64 // 最大阈值
RedirectMode bool // 是否使用302跳转模式
}
type CompressionConfig struct {
@ -39,6 +41,7 @@ type ExtRuleConfig struct {
Target string `json:"Target"` // 目标服务器
SizeThreshold int64 `json:"SizeThreshold"` // 最小阈值
MaxSize int64 `json:"MaxSize"` // 最大阈值
RedirectMode bool `json:"RedirectMode"` // 是否使用302跳转模式
}
// 处理扩展名映射的方法
@ -55,6 +58,7 @@ func (p *PathConfig) ProcessExtensionMap() {
Target: rule.Target,
SizeThreshold: rule.SizeThreshold,
MaxSize: rule.MaxSize,
RedirectMode: rule.RedirectMode,
}
// 处理扩展名列表
@ -87,3 +91,20 @@ func (p *PathConfig) GetProcessedExtTarget(ext string) (string, bool) {
return "", false
}
// GetProcessedExtRule 获取扩展名对应的完整规则信息包括RedirectMode
func (p *PathConfig) GetProcessedExtRule(ext string) (*ExtensionRule, bool) {
if p.ExtRules == nil {
return nil, false
}
for _, rule := range p.ExtRules {
for _, e := range rule.Extensions {
if e == ext {
return &rule, true
}
}
}
return nil, false
}

View File

@ -45,14 +45,15 @@ var hopHeadersBase = map[string]bool{
type ErrorHandler func(w http.ResponseWriter, r *http.Request, err error)
type ProxyHandler struct {
pathMap map[string]config.PathConfig
prefixTree *prefixMatcher // 添加前缀匹配树
client *http.Client
startTime time.Time
config *config.Config
auth *authManager
errorHandler ErrorHandler
Cache *cache.CacheManager
pathMap map[string]config.PathConfig
prefixTree *prefixMatcher // 添加前缀匹配树
client *http.Client
startTime time.Time
config *config.Config
auth *authManager
errorHandler ErrorHandler
Cache *cache.CacheManager
redirectHandler *RedirectHandler // 添加302跳转处理器
}
// 前缀匹配器结构体
@ -166,10 +167,11 @@ func NewProxyHandler(cfg *config.Config) *ProxyHandler {
return nil
},
},
startTime: time.Now(),
config: cfg,
auth: newAuthManager(),
Cache: cacheManager,
startTime: time.Now(),
config: cfg,
auth: newAuthManager(),
Cache: cacheManager,
redirectHandler: NewRedirectHandler(), // 初始化302跳转处理器
errorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
log.Printf("[Error] %s %s -> %v from %s", r.Method, r.URL.Path, err, utils.GetRequestSource(r))
w.WriteHeader(http.StatusInternalServerError)
@ -236,6 +238,13 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
// 检查是否需要进行302跳转
if h.redirectHandler != nil && h.redirectHandler.HandleRedirect(w, r, pathConfig, decodedPath) {
// 如果进行了302跳转直接返回不继续处理
collector.RecordRequest(r.URL.Path, http.StatusFound, time.Since(start), 0, utils.GetClientIP(r), r)
return
}
// 使用统一的路由选择逻辑
targetBase, usedAltTarget := utils.GetTargetURL(h.client, r, pathConfig, decodedPath)

View File

@ -0,0 +1,129 @@
package handler
import (
"log"
"net/http"
"net/url"
"path/filepath"
"proxy-go/internal/config"
"proxy-go/internal/utils"
"strings"
)
// RedirectHandler 处理302跳转逻辑
type RedirectHandler struct{}
// NewRedirectHandler 创建新的跳转处理器
func NewRedirectHandler() *RedirectHandler {
return &RedirectHandler{}
}
// HandleRedirect 处理302跳转请求
func (rh *RedirectHandler) HandleRedirect(w http.ResponseWriter, r *http.Request, pathConfig config.PathConfig, targetPath string) bool {
// 检查是否需要进行302跳转
shouldRedirect, targetURL := rh.shouldRedirect(r, pathConfig, targetPath)
if !shouldRedirect {
return false
}
// 执行302跳转
rh.performRedirect(w, r, targetURL)
return true
}
// shouldRedirect 判断是否应该进行302跳转并返回目标URL
func (rh *RedirectHandler) shouldRedirect(r *http.Request, pathConfig config.PathConfig, targetPath string) (bool, string) {
// 获取文件扩展名
ext := strings.ToLower(filepath.Ext(targetPath))
if ext != "" {
ext = ext[1:] // 去掉点号
}
// 首先检查扩展名规则是否有302跳转配置
if rule, found := pathConfig.GetProcessedExtRule(ext); found && rule.RedirectMode {
// 使用扩展名规则的目标URL进行302跳转
targetURL := rh.buildTargetURL(rule.Target, targetPath, r.URL.RawQuery)
return true, targetURL
}
// 检查通配符规则
if rule, found := pathConfig.GetProcessedExtRule("*"); found && rule.RedirectMode {
// 使用通配符规则的目标URL进行302跳转
targetURL := rh.buildTargetURL(rule.Target, targetPath, r.URL.RawQuery)
return true, targetURL
}
// 检查默认目标是否配置为302跳转
if pathConfig.RedirectMode {
// 使用默认目标URL进行302跳转
targetURL := rh.buildTargetURL(pathConfig.DefaultTarget, targetPath, r.URL.RawQuery)
return true, targetURL
}
return false, ""
}
// buildTargetURL 构建完整的目标URL
func (rh *RedirectHandler) buildTargetURL(baseURL, targetPath, rawQuery string) string {
// URL 解码,然后重新编码,确保特殊字符被正确处理
decodedPath, err := url.QueryUnescape(targetPath)
if err != nil {
// 如果解码失败,使用原始路径
decodedPath = targetPath
}
// 重新编码路径,保留 '/'
parts := strings.Split(decodedPath, "/")
for i, part := range parts {
parts[i] = url.PathEscape(part)
}
encodedPath := strings.Join(parts, "/")
// 构建完整URL
targetURL := baseURL + encodedPath
// 添加查询参数
if rawQuery != "" {
targetURL = targetURL + "?" + rawQuery
}
return targetURL
}
// performRedirect 执行302跳转
func (rh *RedirectHandler) performRedirect(w http.ResponseWriter, r *http.Request, targetURL string) {
// 设置302跳转响应头
w.Header().Set("Location", targetURL)
w.Header().Set("Proxy-Go-Redirect", "1")
// 添加缓存控制头,避免浏览器缓存跳转响应
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
// 设置状态码为302
w.WriteHeader(http.StatusFound)
// 记录跳转日志
clientIP := utils.GetClientIP(r)
log.Printf("[Redirect] %s %s -> 302 %s (%s) from %s",
r.Method, r.URL.Path, targetURL, clientIP, utils.GetRequestSource(r))
}
// IsRedirectEnabled 检查路径配置是否启用了任何形式的302跳转
func (rh *RedirectHandler) IsRedirectEnabled(pathConfig config.PathConfig) bool {
// 检查默认目标是否启用跳转
if pathConfig.RedirectMode {
return true
}
// 检查扩展名规则是否有启用跳转的
for _, rule := range pathConfig.ExtRules {
if rule.RedirectMode {
return true
}
}
return false
}

View File

@ -34,6 +34,7 @@ interface ExtRuleConfig {
Target: string; // 目标服务器
SizeThreshold: number; // 最小阈值(字节)
MaxSize: number; // 最大阈值(字节)
RedirectMode?: boolean; // 是否使用302跳转模式
}
interface PathMapping {
@ -41,6 +42,7 @@ interface PathMapping {
ExtensionMap?: ExtRuleConfig[] // 只支持新格式
SizeThreshold?: number // 保留全局阈值字段(向后兼容)
MaxSize?: number // 保留全局阈值字段(向后兼容)
RedirectMode?: boolean // 是否使用302跳转模式
}
interface CompressionConfig {
@ -77,6 +79,7 @@ export default function ConfigPage() {
const [newPathData, setNewPathData] = useState({
path: "",
defaultTarget: "",
redirectMode: false,
extensionMap: {} as Record<string, string>,
sizeThreshold: 0,
maxSize: 0,
@ -281,6 +284,7 @@ export default function ConfigPage() {
setNewPathData({
path: "",
defaultTarget: "",
redirectMode: false,
extensionMap: {},
sizeThreshold: 0,
maxSize: 0,
@ -327,6 +331,7 @@ export default function ConfigPage() {
setNewPathData({
path: "",
defaultTarget: "",
redirectMode: false,
extensionMap: {},
sizeThreshold: 0,
maxSize: 0,
@ -375,6 +380,7 @@ export default function ConfigPage() {
setNewPathData({
path: "",
defaultTarget: "",
redirectMode: false,
extensionMap: {},
sizeThreshold: 0,
maxSize: 0,
@ -1109,4 +1115,4 @@ const convertBytesToUnit = (bytes: number): { value: number, unit: 'B' | 'KB' |
value: Number((bytes / Math.pow(k, i)).toFixed(2)),
unit: sizes[i]
}
}
}