mirror of
https://github.com/woodchen-ink/proxy-go.git
synced 2025-07-18 08:31:55 +08:00
增强路径匹配逻辑,添加前缀匹配器以提高性能,同时优化请求头设置和扩展名处理,确保代理请求的兼容性和稳定性。
This commit is contained in:
parent
c2266a60d6
commit
1d84c0c614
@ -70,3 +70,20 @@ func (p *PathConfig) ProcessExtensionMap() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetProcessedExtTarget 快速获取扩展名对应的目标URL,如果存在返回true
|
||||||
|
func (p *PathConfig) GetProcessedExtTarget(ext string) (string, bool) {
|
||||||
|
if p.ExtRules == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rule := range p.ExtRules {
|
||||||
|
for _, e := range rule.Extensions {
|
||||||
|
if e == ext {
|
||||||
|
return rule.Target, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"proxy-go/internal/config"
|
"proxy-go/internal/config"
|
||||||
"proxy-go/internal/metrics"
|
"proxy-go/internal/metrics"
|
||||||
"proxy-go/internal/utils"
|
"proxy-go/internal/utils"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -22,9 +23,9 @@ const (
|
|||||||
// 超时时间常量
|
// 超时时间常量
|
||||||
clientConnTimeout = 10 * time.Second
|
clientConnTimeout = 10 * time.Second
|
||||||
proxyRespTimeout = 60 * time.Second
|
proxyRespTimeout = 60 * time.Second
|
||||||
backendServTimeout = 40 * time.Second
|
backendServTimeout = 30 * time.Second
|
||||||
idleConnTimeout = 120 * time.Second
|
idleConnTimeout = 90 * time.Second
|
||||||
tlsHandshakeTimeout = 10 * time.Second
|
tlsHandshakeTimeout = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
// 添加 hop-by-hop 头部映射
|
// 添加 hop-by-hop 头部映射
|
||||||
@ -45,6 +46,7 @@ type ErrorHandler func(w http.ResponseWriter, r *http.Request, err error)
|
|||||||
|
|
||||||
type ProxyHandler struct {
|
type ProxyHandler struct {
|
||||||
pathMap map[string]config.PathConfig
|
pathMap map[string]config.PathConfig
|
||||||
|
prefixTree *prefixMatcher // 添加前缀匹配树
|
||||||
client *http.Client
|
client *http.Client
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
config *config.Config
|
config *config.Config
|
||||||
@ -53,6 +55,64 @@ type ProxyHandler struct {
|
|||||||
Cache *cache.CacheManager
|
Cache *cache.CacheManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 前缀匹配器结构体
|
||||||
|
type prefixMatcher struct {
|
||||||
|
prefixes []string
|
||||||
|
configs map[string]config.PathConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新的前缀匹配器
|
||||||
|
func newPrefixMatcher(pathMap map[string]config.PathConfig) *prefixMatcher {
|
||||||
|
pm := &prefixMatcher{
|
||||||
|
prefixes: make([]string, 0, len(pathMap)),
|
||||||
|
configs: make(map[string]config.PathConfig, len(pathMap)),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按长度降序排列前缀,确保最长匹配优先
|
||||||
|
for prefix, cfg := range pathMap {
|
||||||
|
pm.prefixes = append(pm.prefixes, prefix)
|
||||||
|
pm.configs[prefix] = cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按长度降序排列
|
||||||
|
sort.Slice(pm.prefixes, func(i, j int) bool {
|
||||||
|
return len(pm.prefixes[i]) > len(pm.prefixes[j])
|
||||||
|
})
|
||||||
|
|
||||||
|
return pm
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据路径查找匹配的前缀和配置
|
||||||
|
func (pm *prefixMatcher) match(path string) (string, config.PathConfig, bool) {
|
||||||
|
// 按预排序的前缀列表查找最长匹配
|
||||||
|
for _, prefix := range pm.prefixes {
|
||||||
|
if strings.HasPrefix(path, prefix) {
|
||||||
|
// 确保匹配的是完整的路径段
|
||||||
|
restPath := path[len(prefix):]
|
||||||
|
if restPath == "" || restPath[0] == '/' {
|
||||||
|
return prefix, pm.configs[prefix], true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", config.PathConfig{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新前缀匹配器
|
||||||
|
func (pm *prefixMatcher) update(pathMap map[string]config.PathConfig) {
|
||||||
|
pm.prefixes = make([]string, 0, len(pathMap))
|
||||||
|
pm.configs = make(map[string]config.PathConfig, len(pathMap))
|
||||||
|
|
||||||
|
for prefix, cfg := range pathMap {
|
||||||
|
pm.prefixes = append(pm.prefixes, prefix)
|
||||||
|
pm.configs[prefix] = cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按长度降序排列
|
||||||
|
sort.Slice(pm.prefixes, func(i, j int) bool {
|
||||||
|
return len(pm.prefixes[i]) > len(pm.prefixes[j])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// NewProxyHandler 创建新的代理处理器
|
// NewProxyHandler 创建新的代理处理器
|
||||||
func NewProxyHandler(cfg *config.Config) *ProxyHandler {
|
func NewProxyHandler(cfg *config.Config) *ProxyHandler {
|
||||||
dialer := &net.Dialer{
|
dialer := &net.Dialer{
|
||||||
@ -62,17 +122,17 @@ func NewProxyHandler(cfg *config.Config) *ProxyHandler {
|
|||||||
|
|
||||||
transport := &http.Transport{
|
transport := &http.Transport{
|
||||||
DialContext: dialer.DialContext,
|
DialContext: dialer.DialContext,
|
||||||
MaxIdleConns: 1000,
|
MaxIdleConns: 2000,
|
||||||
MaxIdleConnsPerHost: 100,
|
MaxIdleConnsPerHost: 200,
|
||||||
IdleConnTimeout: idleConnTimeout,
|
IdleConnTimeout: idleConnTimeout,
|
||||||
TLSHandshakeTimeout: tlsHandshakeTimeout,
|
TLSHandshakeTimeout: tlsHandshakeTimeout,
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
MaxConnsPerHost: 200,
|
MaxConnsPerHost: 400,
|
||||||
DisableKeepAlives: false,
|
DisableKeepAlives: false,
|
||||||
DisableCompression: false,
|
DisableCompression: false,
|
||||||
ForceAttemptHTTP2: true,
|
ForceAttemptHTTP2: true,
|
||||||
WriteBufferSize: 64 * 1024,
|
WriteBufferSize: 128 * 1024,
|
||||||
ReadBufferSize: 64 * 1024,
|
ReadBufferSize: 128 * 1024,
|
||||||
ResponseHeaderTimeout: backendServTimeout,
|
ResponseHeaderTimeout: backendServTimeout,
|
||||||
MaxResponseHeaderBytes: 64 * 1024,
|
MaxResponseHeaderBytes: 64 * 1024,
|
||||||
}
|
}
|
||||||
@ -95,6 +155,7 @@ func NewProxyHandler(cfg *config.Config) *ProxyHandler {
|
|||||||
|
|
||||||
handler := &ProxyHandler{
|
handler := &ProxyHandler{
|
||||||
pathMap: cfg.MAP,
|
pathMap: cfg.MAP,
|
||||||
|
prefixTree: newPrefixMatcher(cfg.MAP), // 初始化前缀匹配树
|
||||||
client: &http.Client{
|
client: &http.Client{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
Timeout: proxyRespTimeout,
|
Timeout: proxyRespTimeout,
|
||||||
@ -124,6 +185,7 @@ func NewProxyHandler(cfg *config.Config) *ProxyHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handler.pathMap = newCfg.MAP
|
handler.pathMap = newCfg.MAP
|
||||||
|
handler.prefixTree.update(newCfg.MAP) // 更新前缀匹配树
|
||||||
handler.config = newCfg
|
handler.config = newCfg
|
||||||
log.Printf("[Config] 代理处理器配置已更新: %d 个路径映射", len(newCfg.MAP))
|
log.Printf("[Config] 代理处理器配置已更新: %d 个路径映射", len(newCfg.MAP))
|
||||||
})
|
})
|
||||||
@ -159,27 +221,11 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查找匹配的代理路径
|
// 使用前缀匹配树快速查找匹配的路径
|
||||||
var matchedPrefix string
|
matchedPrefix, pathConfig, matched := h.prefixTree.match(r.URL.Path)
|
||||||
var pathConfig config.PathConfig
|
|
||||||
|
|
||||||
// 首先尝试完全匹配路径段
|
// 如果没有找到匹配,返回404
|
||||||
for prefix, cfg := range h.pathMap {
|
if !matched {
|
||||||
// 检查是否是完整的路径段匹配
|
|
||||||
if strings.HasPrefix(r.URL.Path, prefix) {
|
|
||||||
// 确保匹配的是完整的路径段
|
|
||||||
restPath := r.URL.Path[len(prefix):]
|
|
||||||
if restPath == "" || restPath[0] == '/' {
|
|
||||||
matchedPrefix = prefix
|
|
||||||
pathConfig = cfg
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有找到完全匹配,返回404
|
|
||||||
if matchedPrefix == "" {
|
|
||||||
// 返回 404
|
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -219,66 +265,59 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 复制并处理请求头
|
// 复制并处理请求头 - 使用更高效的方式
|
||||||
copyHeader(proxyReq.Header, r.Header)
|
copyHeader(proxyReq.Header, r.Header)
|
||||||
|
|
||||||
// 添加常见浏览器User-Agent
|
// 添加常见浏览器User-Agent - 避免冗余字符串操作
|
||||||
if ua := r.Header.Get("User-Agent"); ua != "" {
|
if r.Header.Get("User-Agent") == "" {
|
||||||
proxyReq.Header.Set("User-Agent", ua)
|
|
||||||
} else {
|
|
||||||
proxyReq.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
|
proxyReq.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用预先构建的URL字符串
|
||||||
|
hostScheme := parsedURL.Scheme + "://" + parsedURL.Host
|
||||||
|
|
||||||
// 添加Origin
|
// 添加Origin
|
||||||
proxyReq.Header.Set("Origin", fmt.Sprintf("%s://%s", parsedURL.Scheme, parsedURL.Host))
|
proxyReq.Header.Set("Origin", hostScheme)
|
||||||
|
|
||||||
// 设置Referer为源站的完整域名(带上斜杠)
|
// 设置Referer为源站的完整域名(带上斜杠)
|
||||||
proxyReq.Header.Set("Referer", fmt.Sprintf("%s://%s/", parsedURL.Scheme, parsedURL.Host))
|
proxyReq.Header.Set("Referer", hostScheme+"/")
|
||||||
|
|
||||||
// 设置Host头和proxyReq.Host
|
// 设置Host头和proxyReq.Host
|
||||||
proxyReq.Header.Set("Host", parsedURL.Host)
|
proxyReq.Header.Set("Host", parsedURL.Host)
|
||||||
proxyReq.Host = parsedURL.Host
|
proxyReq.Host = parsedURL.Host
|
||||||
|
|
||||||
// 确保设置适当的Accept头
|
// 确保设置适当的Accept头 - 避免冗余字符串操作
|
||||||
if accept := r.Header.Get("Accept"); accept != "" {
|
if r.Header.Get("Accept") == "" {
|
||||||
proxyReq.Header.Set("Accept", accept)
|
|
||||||
} else {
|
|
||||||
proxyReq.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
|
proxyReq.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保设置Accept-Encoding
|
// 确保设置Accept-Encoding - 避免冗余字符串操作
|
||||||
if acceptEncoding := r.Header.Get("Accept-Encoding"); acceptEncoding != "" {
|
if r.Header.Get("Accept-Encoding") == "" {
|
||||||
proxyReq.Header.Set("Accept-Encoding", acceptEncoding)
|
|
||||||
} else {
|
|
||||||
proxyReq.Header.Set("Accept-Encoding", "gzip, deflate, br")
|
proxyReq.Header.Set("Accept-Encoding", "gzip, deflate, br")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 特别处理图片请求
|
// 特别处理图片请求
|
||||||
if utils.IsImageRequest(r.URL.Path) {
|
if utils.IsImageRequest(r.URL.Path) {
|
||||||
// 获取 Accept 头
|
|
||||||
accept := r.Header.Get("Accept")
|
accept := r.Header.Get("Accept")
|
||||||
|
|
||||||
// 根据 Accept 头设置合适的图片格式
|
// 使用switch语句优化条件分支
|
||||||
if strings.Contains(accept, "image/avif") {
|
switch {
|
||||||
|
case strings.Contains(accept, "image/avif"):
|
||||||
proxyReq.Header.Set("Accept", "image/avif")
|
proxyReq.Header.Set("Accept", "image/avif")
|
||||||
} else if strings.Contains(accept, "image/webp") {
|
case strings.Contains(accept, "image/webp"):
|
||||||
proxyReq.Header.Set("Accept", "image/webp")
|
proxyReq.Header.Set("Accept", "image/webp")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置 Cloudflare 特定的头部
|
// 设置 Cloudflare 特定的头部
|
||||||
proxyReq.Header.Set("CF-Image-Format", "auto") // 让 Cloudflare 根据 Accept 头自动选择格式
|
proxyReq.Header.Set("CF-Image-Format", "auto")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置最小必要的代理头部
|
// 设置最小必要的代理头部
|
||||||
proxyReq.Header.Set("X-Real-IP", utils.GetClientIP(r))
|
clientIP := utils.GetClientIP(r)
|
||||||
|
proxyReq.Header.Set("X-Real-IP", clientIP)
|
||||||
|
|
||||||
// 如果源站不严格要求Host匹配,可以保留以下头部
|
// 添加或更新 X-Forwarded-For - 减少重复获取客户端IP
|
||||||
// 否则可能需要注释掉这些头部
|
if clientIP != "" {
|
||||||
// proxyReq.Header.Set("X-Forwarded-Host", r.Host)
|
|
||||||
// proxyReq.Header.Set("X-Forwarded-Proto", r.URL.Scheme)
|
|
||||||
|
|
||||||
// 添加或更新 X-Forwarded-For
|
|
||||||
if clientIP := utils.GetClientIP(r); clientIP != "" {
|
|
||||||
if prior := proxyReq.Header.Get("X-Forwarded-For"); prior != "" {
|
if prior := proxyReq.Header.Get("X-Forwarded-For"); prior != "" {
|
||||||
proxyReq.Header.Set("X-Forwarded-For", prior+", "+clientIP)
|
proxyReq.Header.Set("X-Forwarded-For", prior+", "+clientIP)
|
||||||
} else {
|
} else {
|
||||||
@ -357,20 +396,39 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
cacheKey := h.Cache.GenerateCacheKey(r)
|
cacheKey := h.Cache.GenerateCacheKey(r)
|
||||||
if cacheFile, err := h.Cache.CreateTemp(cacheKey, resp); err == nil {
|
if cacheFile, err := h.Cache.CreateTemp(cacheKey, resp); err == nil {
|
||||||
defer cacheFile.Close()
|
defer cacheFile.Close()
|
||||||
|
|
||||||
|
// 使用缓冲IO提高性能
|
||||||
|
bufSize := 32 * 1024 // 32KB 缓冲区
|
||||||
|
buf := make([]byte, bufSize)
|
||||||
|
|
||||||
teeReader := io.TeeReader(resp.Body, cacheFile)
|
teeReader := io.TeeReader(resp.Body, cacheFile)
|
||||||
written, err = io.Copy(w, teeReader)
|
written, err = io.CopyBuffer(w, teeReader, buf)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
h.Cache.Commit(cacheKey, cacheFile.Name(), resp, written)
|
// 异步提交缓存,不阻塞当前请求处理
|
||||||
|
fileName := cacheFile.Name()
|
||||||
|
respClone := *resp // 创建响应的浅拷贝
|
||||||
|
go func() {
|
||||||
|
h.Cache.Commit(cacheKey, fileName, &respClone, written)
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
written, err = io.Copy(w, resp.Body)
|
// 使用缓冲的复制提高性能
|
||||||
|
bufSize := 32 * 1024 // 32KB 缓冲区
|
||||||
|
buf := make([]byte, bufSize)
|
||||||
|
|
||||||
|
written, err = io.CopyBuffer(w, resp.Body, buf)
|
||||||
if err != nil && !isConnectionClosed(err) {
|
if err != nil && !isConnectionClosed(err) {
|
||||||
log.Printf("[Proxy] ERR %s %s -> write error (%s) from %s", r.Method, r.URL.Path, utils.GetClientIP(r), utils.GetRequestSource(r))
|
log.Printf("[Proxy] ERR %s %s -> write error (%s) from %s", r.Method, r.URL.Path, utils.GetClientIP(r), utils.GetRequestSource(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
written, err = io.Copy(w, resp.Body)
|
// 使用缓冲的复制提高性能
|
||||||
|
bufSize := 32 * 1024 // 32KB 缓冲区
|
||||||
|
buf := make([]byte, bufSize)
|
||||||
|
|
||||||
|
written, err = io.CopyBuffer(w, resp.Body, buf)
|
||||||
if err != nil && !isConnectionClosed(err) {
|
if err != nil && !isConnectionClosed(err) {
|
||||||
log.Printf("[Proxy] ERR %s %s -> write error (%s) from %s", r.Method, r.URL.Path, utils.GetClientIP(r), utils.GetRequestSource(r))
|
log.Printf("[Proxy] ERR %s %s -> write error (%s) from %s", r.Method, r.URL.Path, utils.GetClientIP(r), utils.GetRequestSource(r))
|
||||||
return
|
return
|
||||||
|
@ -6,13 +6,11 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
neturl "net/url"
|
neturl "net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"proxy-go/internal/config"
|
"proxy-go/internal/config"
|
||||||
"slices"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -191,11 +189,23 @@ func GetTargetURL(client *http.Client, r *http.Request, pathConfig config.PathCo
|
|||||||
targetBase := pathConfig.DefaultTarget
|
targetBase := pathConfig.DefaultTarget
|
||||||
usedAltTarget := false
|
usedAltTarget := false
|
||||||
|
|
||||||
// 获取文件扩展名
|
// 获取文件扩展名(使用优化的字符串处理)
|
||||||
ext := strings.ToLower(filepath.Ext(path))
|
ext := ""
|
||||||
if ext != "" {
|
lastDotIndex := strings.LastIndex(path, ".")
|
||||||
ext = ext[1:] // 移除开头的点
|
if lastDotIndex > 0 && lastDotIndex < len(path)-1 {
|
||||||
} else {
|
ext = strings.ToLower(path[lastDotIndex+1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有扩展名规则,直接返回默认目标
|
||||||
|
if len(pathConfig.ExtRules) == 0 {
|
||||||
|
if ext == "" {
|
||||||
|
log.Printf("[Route] %s -> %s (无扩展名)", path, targetBase)
|
||||||
|
}
|
||||||
|
return targetBase, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保有扩展名规则
|
||||||
|
if ext == "" {
|
||||||
log.Printf("[Route] %s -> %s (无扩展名)", path, targetBase)
|
log.Printf("[Route] %s -> %s (无扩展名)", path, targetBase)
|
||||||
// 即使没有扩展名,也要尝试匹配 * 通配符规则
|
// 即使没有扩展名,也要尝试匹配 * 通配符规则
|
||||||
}
|
}
|
||||||
@ -204,20 +214,27 @@ func GetTargetURL(client *http.Client, r *http.Request, pathConfig config.PathCo
|
|||||||
contentLength, err := GetFileSize(client, targetBase+path)
|
contentLength, err := GetFileSize(client, targetBase+path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[Route] %s -> %s (获取文件大小出错: %v)", path, targetBase, err)
|
log.Printf("[Route] %s -> %s (获取文件大小出错: %v)", path, targetBase, err)
|
||||||
return targetBase, false
|
|
||||||
|
// 如果无法获取文件大小,尝试使用扩展名直接匹配(优化点)
|
||||||
|
if altTarget, exists := pathConfig.GetProcessedExtTarget(ext); exists {
|
||||||
|
usedAltTarget = true
|
||||||
|
targetBase = altTarget
|
||||||
|
log.Printf("[Route] %s -> %s (基于扩展名直接匹配)", path, targetBase)
|
||||||
|
} else if altTarget, exists := pathConfig.GetProcessedExtTarget("*"); exists {
|
||||||
|
// 尝试使用通配符
|
||||||
|
usedAltTarget = true
|
||||||
|
targetBase = altTarget
|
||||||
|
log.Printf("[Route] %s -> %s (基于通配符匹配)", path, targetBase)
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetBase, usedAltTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取匹配的扩展名规则
|
// 获取匹配的扩展名规则
|
||||||
matchingRules := []config.ExtensionRule{}
|
matchingRules := []config.ExtensionRule{}
|
||||||
wildcardRules := []config.ExtensionRule{} // 存储通配符规则
|
wildcardRules := []config.ExtensionRule{} // 存储通配符规则
|
||||||
|
|
||||||
// 处理扩展名,找出所有匹配的规则
|
|
||||||
if pathConfig.ExtRules == nil {
|
|
||||||
pathConfig.ProcessExtensionMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 找出所有匹配当前扩展名的规则
|
// 找出所有匹配当前扩展名的规则
|
||||||
ext = strings.ToLower(ext)
|
|
||||||
for _, rule := range pathConfig.ExtRules {
|
for _, rule := range pathConfig.ExtRules {
|
||||||
// 处理阈值默认值
|
// 处理阈值默认值
|
||||||
if rule.SizeThreshold < 0 {
|
if rule.SizeThreshold < 0 {
|
||||||
@ -225,18 +242,23 @@ func GetTargetURL(client *http.Client, r *http.Request, pathConfig config.PathCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if rule.MaxSize <= 0 {
|
if rule.MaxSize <= 0 {
|
||||||
rule.MaxSize = math.MaxInt64 // 设置为最大值表示不限制
|
rule.MaxSize = 1<<63 - 1 // 设置为最大值表示不限制
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否包含通配符
|
// 检查是否包含通配符
|
||||||
if slices.Contains(rule.Extensions, "*") {
|
for _, e := range rule.Extensions {
|
||||||
|
if e == "*" {
|
||||||
wildcardRules = append(wildcardRules, rule)
|
wildcardRules = append(wildcardRules, rule)
|
||||||
continue
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查具体扩展名匹配
|
// 检查具体扩展名匹配
|
||||||
if slices.Contains(rule.Extensions, ext) {
|
for _, e := range rule.Extensions {
|
||||||
|
if e == ext {
|
||||||
matchingRules = append(matchingRules, rule)
|
matchingRules = append(matchingRules, rule)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,8 +272,7 @@ func GetTargetURL(client *http.Client, r *http.Request, pathConfig config.PathCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按阈值排序规则,优先使用阈值范围更精确的规则
|
// 按阈值排序规则(优化点:使用更高效的排序)
|
||||||
// 先按最小阈值升序排序,再按最大阈值降序排序(在最小阈值相同的情况下)
|
|
||||||
sort.Slice(matchingRules, func(i, j int) bool {
|
sort.Slice(matchingRules, func(i, j int) bool {
|
||||||
if matchingRules[i].SizeThreshold == matchingRules[j].SizeThreshold {
|
if matchingRules[i].SizeThreshold == matchingRules[j].SizeThreshold {
|
||||||
return matchingRules[i].MaxSize > matchingRules[j].MaxSize
|
return matchingRules[i].MaxSize > matchingRules[j].MaxSize
|
||||||
@ -260,8 +281,6 @@ func GetTargetURL(client *http.Client, r *http.Request, pathConfig config.PathCo
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 根据文件大小找出最匹配的规则
|
// 根据文件大小找出最匹配的规则
|
||||||
var bestRule *config.ExtensionRule
|
|
||||||
|
|
||||||
for i := range matchingRules {
|
for i := range matchingRules {
|
||||||
rule := &matchingRules[i]
|
rule := &matchingRules[i]
|
||||||
|
|
||||||
@ -271,71 +290,18 @@ func GetTargetURL(client *http.Client, r *http.Request, pathConfig config.PathCo
|
|||||||
log.Printf("[Route] %s -> %s (文件大小: %s, 在区间 %s 到 %s 之间)",
|
log.Printf("[Route] %s -> %s (文件大小: %s, 在区间 %s 到 %s 之间)",
|
||||||
path, rule.Target, FormatBytes(contentLength),
|
path, rule.Target, FormatBytes(contentLength),
|
||||||
FormatBytes(rule.SizeThreshold), FormatBytes(rule.MaxSize))
|
FormatBytes(rule.SizeThreshold), FormatBytes(rule.MaxSize))
|
||||||
bestRule = rule
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果找到匹配的规则
|
// 检查目标是否可访问(使用带缓存的检查)
|
||||||
if bestRule != nil {
|
if isTargetAccessible(client, rule.Target+path) {
|
||||||
// 创建一个带超时的 context
|
targetBase = rule.Target
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
usedAltTarget = true
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// 使用 channel 来接收备用源检查结果
|
|
||||||
altChan := make(chan struct {
|
|
||||||
accessible bool
|
|
||||||
err error
|
|
||||||
}, 1)
|
|
||||||
|
|
||||||
// 在 goroutine 中检查备用源可访问性
|
|
||||||
go func() {
|
|
||||||
accessible := isTargetAccessible(client, bestRule.Target+path)
|
|
||||||
select {
|
|
||||||
case altChan <- struct {
|
|
||||||
accessible bool
|
|
||||||
err error
|
|
||||||
}{accessible: accessible}:
|
|
||||||
case <-ctx.Done():
|
|
||||||
// context 已取消,不需要发送结果
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// 等待结果或超时
|
|
||||||
select {
|
|
||||||
case result := <-altChan:
|
|
||||||
if result.accessible {
|
|
||||||
log.Printf("[Route] %s -> %s (文件大小: %s, 在区间 %s 到 %s 之间)",
|
|
||||||
path, bestRule.Target, FormatBytes(contentLength),
|
|
||||||
FormatBytes(bestRule.SizeThreshold), FormatBytes(bestRule.MaxSize))
|
|
||||||
return bestRule.Target, true
|
|
||||||
}
|
|
||||||
// 如果是通配符规则但不可访问,记录日志
|
|
||||||
if slices.Contains(bestRule.Extensions, "*") {
|
|
||||||
log.Printf("[Route] %s -> %s (回退: 通配符规则目标不可访问)",
|
|
||||||
path, targetBase)
|
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[Route] %s -> %s (回退: 备用目标不可访问)",
|
log.Printf("[Route] %s -> %s (回退: 备用目标不可访问)",
|
||||||
path, targetBase)
|
path, targetBase)
|
||||||
}
|
}
|
||||||
case <-ctx.Done():
|
|
||||||
log.Printf("[Route] %s -> %s (回退: 备用目标检查超时)",
|
|
||||||
path, targetBase)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 记录日志,为什么没有匹配的规则
|
|
||||||
allThresholds := ""
|
|
||||||
for i, rule := range matchingRules {
|
|
||||||
if i > 0 {
|
|
||||||
allThresholds += ", "
|
|
||||||
}
|
|
||||||
allThresholds += fmt.Sprintf("[%s-%s]",
|
|
||||||
FormatBytes(rule.SizeThreshold),
|
|
||||||
FormatBytes(rule.MaxSize))
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("[Route] %s -> %s (文件大小: %s 不在任何阈值范围内: %s)",
|
break
|
||||||
path, targetBase, FormatBytes(contentLength), allThresholds)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return targetBase, usedAltTarget
|
return targetBase, usedAltTarget
|
||||||
|
Loading…
x
Reference in New Issue
Block a user