mirror of
https://github.com/woodchen-ink/proxy-go.git
synced 2025-07-18 08:31:55 +08:00
125 lines
3.5 KiB
Go
125 lines
3.5 KiB
Go
package handler
|
||
|
||
import (
|
||
"log"
|
||
"net/http"
|
||
"net/url"
|
||
"proxy-go/internal/config"
|
||
"proxy-go/internal/service"
|
||
"proxy-go/internal/utils"
|
||
"strings"
|
||
|
||
"github.com/woodchen-ink/go-web-utils/iputil"
|
||
)
|
||
|
||
// RedirectHandler 处理302跳转逻辑
|
||
type RedirectHandler struct {
|
||
ruleService *service.RuleService
|
||
}
|
||
|
||
// NewRedirectHandler 创建新的跳转处理器
|
||
func NewRedirectHandler(ruleService *service.RuleService) *RedirectHandler {
|
||
return &RedirectHandler{
|
||
ruleService: ruleService,
|
||
}
|
||
}
|
||
|
||
// HandleRedirect 处理302跳转请求
|
||
func (rh *RedirectHandler) HandleRedirect(w http.ResponseWriter, r *http.Request, pathConfig config.PathConfig, targetPath string, client *http.Client) bool {
|
||
// 检查是否需要进行302跳转
|
||
shouldRedirect, targetURL := rh.shouldRedirect(r, pathConfig, targetPath, client)
|
||
|
||
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, client *http.Client) (bool, string) {
|
||
// 使用service包的规则选择函数,传递请求的域名
|
||
result := rh.ruleService.SelectRuleForRedirect(client, pathConfig, targetPath, r.Host)
|
||
|
||
if result.ShouldRedirect {
|
||
// 构建完整的目标URL
|
||
targetURL := rh.buildTargetURL(result.TargetURL, targetPath, r.URL.RawQuery)
|
||
|
||
if result.Rule != nil {
|
||
log.Printf("[Redirect] %s -> 使用选中规则进行302跳转 (域名: %s): %s", targetPath, r.Host, targetURL)
|
||
} else {
|
||
log.Printf("[Redirect] %s -> 使用默认目标进行302跳转 (域名: %s): %s", targetPath, r.Host, targetURL)
|
||
}
|
||
|
||
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 := iputil.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
|
||
}
|