mirror of
https://github.com/woodchen-ink/proxy-go.git
synced 2025-07-18 08:31:55 +08:00
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:
parent
88726d9d8f
commit
9c3c48f4b6
53
internal/cache/manager.go
vendored
53
internal/cache/manager.go
vendored
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user