feat(cache,middleware): Enhance caching mechanism for fixed path proxy

- Modify CacheKey to use a simplified string representation of Vary headers
- Update GenerateCacheKey to handle Vary headers more efficiently
- Implement caching in FixedPathProxyMiddleware with cache hit/miss tracking
- Improve response handling by reading entire response body before writing
- Add error handling for connection-related issues during response writing
This commit is contained in:
wood chen 2025-02-15 17:14:29 +08:00
parent 88726d9d8f
commit 9c3c48f4b6
2 changed files with 78 additions and 38 deletions

View File

@ -19,42 +19,23 @@ import (
// CacheKey 用于标识缓存项的唯一键 // CacheKey 用于标识缓存项的唯一键
type CacheKey struct { type CacheKey struct {
URL string URL string
AcceptHeaders string AcceptHeaders string
UserAgent string UserAgent string
VaryHeadersMap map[string]string // 存储 Vary 头部的值 VaryHeaders string // 存储 Vary 头部的值格式key1=value1&key2=value2
} }
// String 实现 Stringer 接口,用于生成唯一的字符串表示 // String 实现 Stringer 接口,用于生成唯一的字符串表示
func (k CacheKey) String() string { func (k CacheKey) String() string {
// 将 VaryHeadersMap 转换为有序的字符串 return fmt.Sprintf("%s|%s|%s|%s", k.URL, k.AcceptHeaders, k.UserAgent, k.VaryHeaders)
var varyPairs []string
for key, value := range k.VaryHeadersMap {
varyPairs = append(varyPairs, key+"="+value)
}
sort.Strings(varyPairs)
varyStr := strings.Join(varyPairs, "&")
return fmt.Sprintf("%s|%s|%s|%s", k.URL, k.AcceptHeaders, k.UserAgent, varyStr)
} }
// Equal 比较两个 CacheKey 是否相等 // Equal 比较两个 CacheKey 是否相等
func (k CacheKey) Equal(other CacheKey) bool { func (k CacheKey) Equal(other CacheKey) bool {
if k.URL != other.URL || k.AcceptHeaders != other.AcceptHeaders || k.UserAgent != other.UserAgent { return k.URL == other.URL &&
return false k.AcceptHeaders == other.AcceptHeaders &&
} k.UserAgent == other.UserAgent &&
k.VaryHeaders == other.VaryHeaders
if len(k.VaryHeadersMap) != len(other.VaryHeadersMap) {
return false
}
for key, value := range k.VaryHeadersMap {
if otherValue, ok := other.VaryHeadersMap[key]; !ok || value != otherValue {
return false
}
}
return true
} }
// Hash 生成 CacheKey 的哈希值 // Hash 生成 CacheKey 的哈希值
@ -128,10 +109,22 @@ func NewCacheManager(cacheDir string) (*CacheManager, error) {
// GenerateCacheKey 生成缓存键 // GenerateCacheKey 生成缓存键
func (cm *CacheManager) GenerateCacheKey(r *http.Request) CacheKey { func (cm *CacheManager) GenerateCacheKey(r *http.Request) CacheKey {
// 处理 Vary 头部
varyHeaders := make([]string, 0)
for _, vary := range strings.Split(r.Header.Get("Vary"), ",") {
vary = strings.TrimSpace(vary)
if vary != "" {
value := r.Header.Get(vary)
varyHeaders = append(varyHeaders, vary+"="+value)
}
}
sort.Strings(varyHeaders)
return CacheKey{ return CacheKey{
URL: r.URL.String(), URL: r.URL.String(),
AcceptHeaders: r.Header.Get("Accept"), AcceptHeaders: r.Header.Get("Accept"),
UserAgent: r.Header.Get("User-Agent"), UserAgent: r.Header.Get("User-Agent"),
VaryHeaders: strings.Join(varyHeaders, "&"),
} }
} }
@ -168,7 +161,9 @@ func (cm *CacheManager) Get(key CacheKey, r *http.Request) (*CacheItem, bool, bo
// 检查 Vary 头部 // 检查 Vary 头部
for _, varyHeader := range item.VaryHeaders { for _, varyHeader := range item.VaryHeaders {
if r.Header.Get(varyHeader) != key.VaryHeadersMap[varyHeader] { requestValue := r.Header.Get(varyHeader)
varyPair := varyHeader + "=" + requestValue
if !strings.Contains(key.VaryHeaders, varyPair) {
cm.missCount.Add(1) cm.missCount.Add(1)
return nil, false, false return nil, false, false
} }

View File

@ -5,6 +5,7 @@ import (
"io" "io"
"log" "log"
"net/http" "net/http"
"proxy-go/internal/cache"
"proxy-go/internal/config" "proxy-go/internal/config"
"proxy-go/internal/metrics" "proxy-go/internal/metrics"
"proxy-go/internal/utils" "proxy-go/internal/utils"
@ -19,6 +20,16 @@ type FixedPathConfig struct {
TargetURL string `json:"TargetURL"` TargetURL string `json:"TargetURL"`
} }
var fixedPathCache *cache.CacheManager
func init() {
var err error
fixedPathCache, err = cache.NewCacheManager("data/fixed_path_cache")
if err != nil {
log.Printf("[Cache] Failed to initialize fixed path cache manager: %v", err)
}
}
func FixedPathProxyMiddleware(configs []config.FixedPathConfig) func(http.Handler) http.Handler { func FixedPathProxyMiddleware(configs []config.FixedPathConfig) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -34,6 +45,23 @@ func FixedPathProxyMiddleware(configs []config.FixedPathConfig) func(http.Handle
targetPath := strings.TrimPrefix(r.URL.Path, cfg.Path) targetPath := strings.TrimPrefix(r.URL.Path, cfg.Path)
targetURL := cfg.TargetURL + targetPath targetURL := cfg.TargetURL + targetPath
// 检查是否可以使用缓存
if r.Method == http.MethodGet && fixedPathCache != nil {
cacheKey := fixedPathCache.GenerateCacheKey(r)
if item, hit, notModified := fixedPathCache.Get(cacheKey, r); hit {
// 从缓存提供响应
w.Header().Set("Content-Type", item.ContentType)
w.Header().Set("Proxy-Go-Cache", "HIT")
if notModified {
w.WriteHeader(http.StatusNotModified)
return
}
http.ServeFile(w, r, item.FilePath)
collector.RecordRequest(r.URL.Path, http.StatusOK, time.Since(startTime), item.Size, utils.GetClientIP(r), r)
return
}
}
proxyReq, err := http.NewRequest(r.Method, targetURL, r.Body) proxyReq, err := http.NewRequest(r.Method, targetURL, r.Body)
if err != nil { if err != nil {
http.Error(w, "Error creating proxy request", http.StatusInternalServerError) http.Error(w, "Error creating proxy request", http.StatusInternalServerError)
@ -66,24 +94,41 @@ func FixedPathProxyMiddleware(configs []config.FixedPathConfig) func(http.Handle
} }
defer resp.Body.Close() defer resp.Body.Close()
// 读取响应体
body, err := io.ReadAll(resp.Body)
if err != nil {
http.Error(w, "Error reading response", http.StatusInternalServerError)
log.Printf("Error reading response: %v", err)
return
}
// 如果是GET请求且响应成功尝试缓存
if r.Method == http.MethodGet && resp.StatusCode == http.StatusOK && fixedPathCache != nil {
cacheKey := fixedPathCache.GenerateCacheKey(r)
if _, err := fixedPathCache.Put(cacheKey, resp, body); err != nil {
log.Printf("[Cache] Failed to cache %s: %v", targetURL, err)
}
}
// 复制响应头 // 复制响应头
for key, values := range resp.Header { for key, values := range resp.Header {
for _, value := range values { for _, value := range values {
w.Header().Add(key, value) w.Header().Add(key, value)
} }
} }
w.Header().Set("Proxy-Go-Cache", "MISS")
// 设置响应状态码 // 设置响应状态码
w.WriteHeader(resp.StatusCode) w.WriteHeader(resp.StatusCode)
// 复制响应体 // 写入响应体
bytesCopied, err := io.Copy(w, resp.Body) written, err := w.Write(body)
if err := handleCopyError(err); err != nil { if err != nil && !isConnectionClosed(err) {
log.Printf("[%s] Error copying response: %v", utils.GetClientIP(r), err) log.Printf("[%s] Error writing response: %v", utils.GetClientIP(r), err)
} }
// 记录统计信息 // 记录统计信息
collector.RecordRequest(r.URL.Path, resp.StatusCode, time.Since(startTime), bytesCopied, utils.GetClientIP(r), r) collector.RecordRequest(r.URL.Path, resp.StatusCode, time.Since(startTime), int64(written), utils.GetClientIP(r), r)
return return
} }
@ -95,9 +140,9 @@ func FixedPathProxyMiddleware(configs []config.FixedPathConfig) func(http.Handle
} }
} }
func handleCopyError(err error) error { func isConnectionClosed(err error) bool {
if err == nil { if err == nil {
return nil return false
} }
// 忽略常见的连接关闭错误 // 忽略常见的连接关闭错误
@ -105,8 +150,8 @@ func handleCopyError(err error) error {
errors.Is(err, syscall.ECONNRESET) || // connection reset by peer errors.Is(err, syscall.ECONNRESET) || // connection reset by peer
strings.Contains(err.Error(), "broken pipe") || strings.Contains(err.Error(), "broken pipe") ||
strings.Contains(err.Error(), "connection reset by peer") { strings.Contains(err.Error(), "connection reset by peer") {
return nil return true
} }
return err return false
} }