feat(cache,proxy): 增强代理缓存功能
- 添加 min_uses 阈值支持,请求次数达标才缓存 - 添加 cache_lock_timeout 配置,防止缓存锁无限等待 - 添加条件请求支持 (If-Modified-Since/If-None-Match),处理 304 响应 - 添加 background_update_disable 配置,允许禁用后台更新 - 添加 cache_ignore_headers 配置,缓存时忽略指定响应头 - 添加 methods 配置,指定可缓存的 HTTP 方法 - 改进路径匹配逻辑,支持精确匹配和通配符匹配 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
48d8c06e31
commit
0a7f7170d5
116
internal/cache/file_cache.go
vendored
116
internal/cache/file_cache.go
vendored
@ -22,6 +22,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -300,6 +301,11 @@ type ProxyCacheEntry struct {
|
|||||||
Data []byte
|
Data []byte
|
||||||
Status int
|
Status int
|
||||||
MaxAge time.Duration
|
MaxAge time.Duration
|
||||||
|
Uses atomic.Int32 // 访问计数,用于 min_uses 阈值检查
|
||||||
|
Updating atomic.Bool // 后台更新标志,表示正在后台刷新
|
||||||
|
LastModified string // Last-Modified 响应头,用于条件请求
|
||||||
|
ETag string // ETag 响应头,用于条件请求
|
||||||
|
LastValidated time.Time // 最后验证时间,用于防止验证循环
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProxyCache 代理响应缓存,支持缓存锁防击穿。
|
// ProxyCache 代理响应缓存,支持缓存锁防击穿。
|
||||||
@ -356,6 +362,9 @@ func (c *ProxyCache) Get(hashKey uint64, origKey string) (*ProxyCacheEntry, bool
|
|||||||
return nil, false, false
|
return nil, false, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 增加访问计数(原子操作,用于 min_uses 阈值检查)
|
||||||
|
entry.Uses.Add(1)
|
||||||
|
|
||||||
// 检查是否过期
|
// 检查是否过期
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
expired := now.Sub(entry.Created) > entry.MaxAge
|
expired := now.Sub(entry.Created) > entry.MaxAge
|
||||||
@ -423,6 +432,50 @@ func (c *ProxyCache) AcquireLock(hashKey uint64) <-chan struct{} {
|
|||||||
return nil // 获得锁,应该生成缓存
|
return nil // 获得锁,应该生成缓存
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AcquireLockWithTimeout 获取缓存生成锁(带超时)。
|
||||||
|
// 返回值:
|
||||||
|
// - waitCh != nil && timedOut == false: 需要等待其他请求完成
|
||||||
|
// - waitCh == nil && timedOut == false: 获得锁,应该生成缓存
|
||||||
|
// - timedOut == true: 超时,应该放弃缓存直接请求上游
|
||||||
|
func (c *ProxyCache) AcquireLockWithTimeout(hashKey uint64, timeout time.Duration) (waitCh <-chan struct{}, timedOut bool) {
|
||||||
|
if !c.cacheLock {
|
||||||
|
return nil, false // 不使用缓存锁
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
// 检查是否已有缓存
|
||||||
|
if _, ok := c.entries[hashKey]; ok {
|
||||||
|
c.mu.Unlock()
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有 pending 请求
|
||||||
|
if pending, ok := c.pending[hashKey]; ok {
|
||||||
|
c.mu.Unlock()
|
||||||
|
// 有其他请求正在生成,需要等待
|
||||||
|
if timeout > 0 {
|
||||||
|
// 带超时等待
|
||||||
|
select {
|
||||||
|
case <-pending.done:
|
||||||
|
// 刚刚完成,重新检查缓存
|
||||||
|
return nil, false
|
||||||
|
case <-time.After(timeout):
|
||||||
|
// 超时
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pending.done, false // 无限等待
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新的 pending 请求
|
||||||
|
pending := &pendingRequest{
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
c.pending[hashKey] = pending
|
||||||
|
c.mu.Unlock()
|
||||||
|
return nil, false // 获得锁,应该生成缓存
|
||||||
|
}
|
||||||
|
|
||||||
// ReleaseLock 释放缓存生成锁。
|
// ReleaseLock 释放缓存生成锁。
|
||||||
func (c *ProxyCache) ReleaseLock(hashKey uint64, err error) {
|
func (c *ProxyCache) ReleaseLock(hashKey uint64, err error) {
|
||||||
if !c.cacheLock {
|
if !c.cacheLock {
|
||||||
@ -442,10 +495,26 @@ func (c *ProxyCache) ReleaseLock(hashKey uint64, err error) {
|
|||||||
// MatchRule 检查请求是否匹配缓存规则。
|
// MatchRule 检查请求是否匹配缓存规则。
|
||||||
func (c *ProxyCache) MatchRule(path, method string, status int) *ProxyCacheRule {
|
func (c *ProxyCache) MatchRule(path, method string, status int) *ProxyCacheRule {
|
||||||
for _, rule := range c.rules {
|
for _, rule := range c.rules {
|
||||||
// 检查路径匹配(简单前缀匹配)
|
// 检查路径匹配
|
||||||
if rule.Path != "" && !MatchPattern(rule.Path, path) {
|
if rule.Path != "" {
|
||||||
|
// 如果路径以 / 结尾,使用前缀匹配
|
||||||
|
// 如果路径包含 *,使用通配符匹配
|
||||||
|
// 否则使用前缀匹配(允许 /api 匹配 /api/users)
|
||||||
|
if strings.HasSuffix(rule.Path, "/") {
|
||||||
|
if !strings.HasPrefix(path, rule.Path) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
} else if strings.Contains(rule.Path, "*") {
|
||||||
|
if !MatchPattern(rule.Path, path) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 精确匹配或前缀匹配
|
||||||
|
if path != rule.Path && !strings.HasPrefix(path, rule.Path+"/") && !strings.HasPrefix(path, rule.Path+"?") && len(path) <= len(rule.Path) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 检查方法
|
// 检查方法
|
||||||
if len(rule.Methods) > 0 && !slices.Contains(rule.Methods, method) {
|
if len(rule.Methods) > 0 && !slices.Contains(rule.Methods, method) {
|
||||||
@ -495,6 +564,49 @@ func (c *ProxyCache) Clear() {
|
|||||||
c.pending = make(map[uint64]*pendingRequest)
|
c.pending = make(map[uint64]*pendingRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RefreshTTL 刷新缓存条目的 TTL(用于 304 响应处理)。
|
||||||
|
// 不替换缓存内容,只更新验证时间和验证头。
|
||||||
|
// 返回是否成功(条目可能已被驱逐)。
|
||||||
|
func (c *ProxyCache) RefreshTTL(hashKey uint64, origKey string, newHeaders map[string]string) bool {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
entry, ok := c.entries[hashKey]
|
||||||
|
if !ok || entry.OrigKey != origKey {
|
||||||
|
return false // 条目已被驱逐
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新验证时间(不更新 Created,保持 LRU 顺序)
|
||||||
|
entry.LastValidated = time.Now()
|
||||||
|
|
||||||
|
// 更新验证头(如果提供)
|
||||||
|
if newHeaders != nil {
|
||||||
|
if lm, ok := newHeaders["Last-Modified"]; ok {
|
||||||
|
entry.LastModified = lm
|
||||||
|
}
|
||||||
|
if et, ok := newHeaders["ETag"]; ok {
|
||||||
|
entry.ETag = et
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetValidationHeaders 设置缓存条目的验证头(Last-Modified 和 ETag)。
|
||||||
|
func (c *ProxyCache) SetValidationHeaders(hashKey uint64, origKey string, lastModified, etag string) bool {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
entry, ok := c.entries[hashKey]
|
||||||
|
if !ok || entry.OrigKey != origKey {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.LastModified = lastModified
|
||||||
|
entry.ETag = etag
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Stats 返回代理缓存统计。
|
// Stats 返回代理缓存统计。
|
||||||
func (c *ProxyCache) Stats() ProxyCacheStats {
|
func (c *ProxyCache) Stats() ProxyCacheStats {
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
|
|||||||
@ -598,6 +598,12 @@ type ProxyCacheConfig struct {
|
|||||||
StaleWhileRevalidate time.Duration `yaml:"stale_while_revalidate"`
|
StaleWhileRevalidate time.Duration `yaml:"stale_while_revalidate"`
|
||||||
Enabled bool `yaml:"enabled"`
|
Enabled bool `yaml:"enabled"`
|
||||||
CacheLock bool `yaml:"cache_lock"`
|
CacheLock bool `yaml:"cache_lock"`
|
||||||
|
Methods []string `yaml:"methods"`
|
||||||
|
MinUses int `yaml:"min_uses"` // 缓存阈值,请求次数达到此值才缓存
|
||||||
|
CacheLockTimeout time.Duration `yaml:"cache_lock_timeout"` // 缓存锁超时时间
|
||||||
|
BackgroundUpdateDisable bool `yaml:"background_update_disable"` // 禁用后台更新(默认 false = 启用后台更新)
|
||||||
|
CacheIgnoreHeaders []string `yaml:"cache_ignore_headers"` // 缓存时忽略的响应头
|
||||||
|
Revalidate bool `yaml:"revalidate"` // 启用条件请求(If-Modified-Since/If-None-Match)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProxyCacheValidConfig 缓存有效期分段配置。
|
// ProxyCacheValidConfig 缓存有效期分段配置。
|
||||||
|
|||||||
@ -404,8 +404,14 @@ func GenerateConfigYAML(cfg *Config) ([]byte, error) {
|
|||||||
buf.WriteString(" # cache: # 代理缓存\n")
|
buf.WriteString(" # cache: # 代理缓存\n")
|
||||||
buf.WriteString(" # enabled: false\n")
|
buf.WriteString(" # enabled: false\n")
|
||||||
buf.WriteString(" # max_age: 60s\n")
|
buf.WriteString(" # max_age: 60s\n")
|
||||||
|
buf.WriteString(" # methods: [GET, HEAD] # 可缓存的 HTTP 方法(默认 GET, HEAD)\n")
|
||||||
|
buf.WriteString(" # min_uses: 1 # 缓存阈值,请求次数达到此值才缓存(默认 1)\n")
|
||||||
buf.WriteString(" # cache_lock: true # 防止缓存击穿\n")
|
buf.WriteString(" # cache_lock: true # 防止缓存击穿\n")
|
||||||
|
buf.WriteString(" # cache_lock_timeout: 5s # 缓存锁超时时间(默认 5s)\n")
|
||||||
buf.WriteString(" # stale_while_revalidate: 30s\n")
|
buf.WriteString(" # stale_while_revalidate: 30s\n")
|
||||||
|
buf.WriteString(" # background_update_disable: false # 禁用后台更新(默认启用)\n")
|
||||||
|
buf.WriteString(" # cache_ignore_headers: [] # 缓存时忽略的响应头\n")
|
||||||
|
buf.WriteString(" # revalidate: false # 启用条件请求(默认关闭)\n")
|
||||||
buf.WriteString(" # cache_valid: # 按 HTTP 状态码细分缓存时间(可选,未配置时使用 max_age)\n")
|
buf.WriteString(" # cache_valid: # 按 HTTP 状态码细分缓存时间(可选,未配置时使用 max_age)\n")
|
||||||
buf.WriteString(" # ok: 10m # 200-299 缓存 10 分钟\n")
|
buf.WriteString(" # ok: 10m # 200-299 缓存 10 分钟\n")
|
||||||
buf.WriteString(" # redirect: 1h # 301/302 缓存 1 小时\n")
|
buf.WriteString(" # redirect: 1h # 301/302 缓存 1 小时\n")
|
||||||
|
|||||||
@ -169,8 +169,15 @@ func NewProxy(cfg *config.ProxyConfig, targets []*loadbalance.Target, transportC
|
|||||||
if cfg.Cache.Enabled {
|
if cfg.Cache.Enabled {
|
||||||
rules := make([]cache.ProxyCacheRule, 0)
|
rules := make([]cache.ProxyCacheRule, 0)
|
||||||
if cfg.Cache.MaxAge > 0 {
|
if cfg.Cache.MaxAge > 0 {
|
||||||
|
// 使用配置中的方法,若为空则使用默认值 GET, HEAD (nginx 默认行为)
|
||||||
|
methods := cfg.Cache.Methods
|
||||||
|
if len(methods) == 0 {
|
||||||
|
methods = []string{"GET", "HEAD"}
|
||||||
|
}
|
||||||
rules = append(rules, cache.ProxyCacheRule{
|
rules = append(rules, cache.ProxyCacheRule{
|
||||||
Path: cfg.Path,
|
Path: cfg.Path,
|
||||||
|
Methods: methods,
|
||||||
|
Statuses: nil, // nil = 所有可缓存状态码 (由 getCacheDuration 处理)
|
||||||
MaxAge: cfg.Cache.MaxAge,
|
MaxAge: cfg.Cache.MaxAge,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -590,6 +597,15 @@ func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
|||||||
|
|
||||||
// 尝试从缓存获取(如果启用)
|
// 尝试从缓存获取(如果启用)
|
||||||
if p.cache != nil && attempt == 0 {
|
if p.cache != nil && attempt == 0 {
|
||||||
|
// 检查请求方法是否允许缓存
|
||||||
|
method := string(ctx.Request.Header.Method())
|
||||||
|
path := string(ctx.Request.URI().Path())
|
||||||
|
rule := p.cache.MatchRule(path, method, 0)
|
||||||
|
if rule == nil {
|
||||||
|
// 方法不在允许列表中,跳过缓存
|
||||||
|
goto proxyRequest
|
||||||
|
}
|
||||||
|
|
||||||
hashKey, origKey := p.buildCacheKeyHash(ctx)
|
hashKey, origKey := p.buildCacheKeyHash(ctx)
|
||||||
if entry, ok, stale := p.cache.Get(hashKey, origKey); ok {
|
if entry, ok, stale := p.cache.Get(hashKey, origKey); ok {
|
||||||
// 缓存命中
|
// 缓存命中
|
||||||
@ -605,8 +621,13 @@ func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 过期缓存,尝试后台刷新,同时返回旧数据
|
// 过期缓存,尝试后台刷新,同时返回旧数据
|
||||||
|
if !p.config.Cache.BackgroundUpdateDisable {
|
||||||
go p.backgroundRefresh(ctx, target, hashKey, origKey)
|
entry.Updating.Store(true)
|
||||||
|
go func() {
|
||||||
|
defer entry.Updating.Store(false)
|
||||||
|
p.backgroundRefresh(ctx, target, hashKey, origKey)
|
||||||
|
}()
|
||||||
|
}
|
||||||
upstreamAddr = "CACHE"
|
upstreamAddr = "CACHE"
|
||||||
upstreamStatus = entry.Status
|
upstreamStatus = entry.Status
|
||||||
|
|
||||||
@ -618,10 +639,18 @@ func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否需要缓存锁(防止缓存击穿)
|
// 检查是否需要缓存锁(防止缓存击穿)
|
||||||
if done := p.cache.AcquireLock(hashKey); done != nil {
|
timeout := p.config.Cache.CacheLockTimeout
|
||||||
|
if timeout == 0 && p.config.Cache.CacheLock {
|
||||||
|
timeout = 5 * time.Second // nginx 默认 5s
|
||||||
|
}
|
||||||
|
waitCh, timedOut := p.cache.AcquireLockWithTimeout(hashKey, timeout)
|
||||||
|
if timedOut {
|
||||||
|
// 超时,跳过缓存直接请求上游
|
||||||
|
// 不缓存响应(nginx 行为)
|
||||||
|
} else if waitCh != nil {
|
||||||
// 有其他请求正在生成缓存,等待
|
// 有其他请求正在生成缓存,等待
|
||||||
loadbalance.DecrementConnections(target)
|
loadbalance.DecrementConnections(target)
|
||||||
<-done
|
<-waitCh
|
||||||
// 重新尝试获取缓存
|
// 重新尝试获取缓存
|
||||||
|
|
||||||
if entry, ok, _ := p.cache.Get(hashKey, origKey); ok {
|
if entry, ok, _ := p.cache.Get(hashKey, origKey); ok {
|
||||||
@ -639,6 +668,7 @@ func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proxyRequest:
|
||||||
// 执行代理请求
|
// 执行代理请求
|
||||||
timing.MarkConnectStart()
|
timing.MarkConnectStart()
|
||||||
err := client.Do(req, &ctx.Response)
|
err := client.Do(req, &ctx.Response)
|
||||||
@ -723,8 +753,25 @@ func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
|||||||
|
|
||||||
// 存入缓存(如果启用且响应可缓存)
|
// 存入缓存(如果启用且响应可缓存)
|
||||||
if p.cache != nil {
|
if p.cache != nil {
|
||||||
|
// 再次检查方法是否允许缓存
|
||||||
|
method := string(ctx.Request.Header.Method())
|
||||||
|
path := string(ctx.Request.URI().Path())
|
||||||
|
if rule := p.cache.MatchRule(path, method, statusCode); rule == nil {
|
||||||
|
// 方法或状态码不在允许列表中,不缓存
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
hashKey, origKey := p.buildCacheKeyHash(ctx)
|
hashKey, origKey := p.buildCacheKeyHash(ctx)
|
||||||
if statusCode >= 200 && statusCode < 300 {
|
if statusCode >= 200 && statusCode < 300 {
|
||||||
|
// 检查 MinUses 阈值
|
||||||
|
if entry, ok, _ := p.cache.Get(hashKey, origKey); ok {
|
||||||
|
minUses := p.config.Cache.MinUses
|
||||||
|
if minUses > 0 && entry.Uses.Load() < int32(minUses) {
|
||||||
|
p.cache.ReleaseLock(hashKey, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 提取响应头(使用 pool 复用 map)
|
// 提取响应头(使用 pool 复用 map)
|
||||||
headers, ok := headersPool.Get().(map[string]string)
|
headers, ok := headersPool.Get().(map[string]string)
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -733,10 +780,31 @@ func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
|||||||
for k := range headers {
|
for k := range headers {
|
||||||
delete(headers, k)
|
delete(headers, k)
|
||||||
}
|
}
|
||||||
|
// 构建忽略头部查找表(大小写不敏感)
|
||||||
|
ignoreSet := make(map[string]bool, len(p.config.Cache.CacheIgnoreHeaders))
|
||||||
|
for _, h := range p.config.Cache.CacheIgnoreHeaders {
|
||||||
|
ignoreSet[strings.ToLower(h)] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastModified, etag string
|
||||||
for key, value := range ctx.Response.Header.All() {
|
for key, value := range ctx.Response.Header.All() {
|
||||||
|
headerName := strings.ToLower(string(key))
|
||||||
|
if ignoreSet[headerName] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
headers[string(key)] = string(value)
|
headers[string(key)] = string(value)
|
||||||
|
|
||||||
|
switch headerName {
|
||||||
|
case "last-modified":
|
||||||
|
lastModified = string(value)
|
||||||
|
case "etag":
|
||||||
|
etag = string(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
p.cache.Set(hashKey, origKey, ctx.Response.Body(), headers, statusCode, p.getCacheDuration(statusCode))
|
p.cache.Set(hashKey, origKey, ctx.Response.Body(), headers, statusCode, p.getCacheDuration(statusCode))
|
||||||
|
if lastModified != "" || etag != "" {
|
||||||
|
p.cache.SetValidationHeaders(hashKey, origKey, lastModified, etag)
|
||||||
|
}
|
||||||
// 注意:不能 Put 回 pool,因为 cache.Set 存储了 map 引用
|
// 注意:不能 Put 回 pool,因为 cache.Set 存储了 map 引用
|
||||||
// 后续 writeCachedResponse 会读取该 map
|
// 后续 writeCachedResponse 会读取该 map
|
||||||
}
|
}
|
||||||
@ -1312,6 +1380,18 @@ func (p *Proxy) backgroundRefresh(ctx *fasthttp.RequestCtx, target *loadbalance.
|
|||||||
// 复制原始请求
|
// 复制原始请求
|
||||||
ctx.Request.CopyTo(req)
|
ctx.Request.CopyTo(req)
|
||||||
|
|
||||||
|
// 如果启用 Revalidate,添加条件请求头
|
||||||
|
if p.config.Cache.Revalidate {
|
||||||
|
if entry, ok, _ := p.cache.Get(hashKey, origKey); ok {
|
||||||
|
if entry.LastModified != "" {
|
||||||
|
req.Header.Set("If-Modified-Since", entry.LastModified)
|
||||||
|
}
|
||||||
|
if entry.ETag != "" {
|
||||||
|
req.Header.Set("If-None-Match", entry.ETag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 获取客户端
|
// 获取客户端
|
||||||
client := p.getClient(target.URL)
|
client := p.getClient(target.URL)
|
||||||
if client == nil {
|
if client == nil {
|
||||||
@ -1325,6 +1405,19 @@ func (p *Proxy) backgroundRefresh(ctx *fasthttp.RequestCtx, target *loadbalance.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理 304 Not Modified 响应
|
||||||
|
if resp.StatusCode() == 304 {
|
||||||
|
newHeaders := make(map[string]string)
|
||||||
|
if lm := resp.Header.Peek("Last-Modified"); len(lm) > 0 {
|
||||||
|
newHeaders["Last-Modified"] = string(lm)
|
||||||
|
}
|
||||||
|
if et := resp.Header.Peek("ETag"); len(et) > 0 {
|
||||||
|
newHeaders["ETag"] = string(et)
|
||||||
|
}
|
||||||
|
p.cache.RefreshTTL(hashKey, origKey, newHeaders)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 提取响应头(使用 pool 复用 map)
|
// 提取响应头(使用 pool 复用 map)
|
||||||
headers, ok := headersPool.Get().(map[string]string)
|
headers, ok := headersPool.Get().(map[string]string)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user