Replaces fnv.New64a() with direct inline hash computation over
fasthttp's []byte slices, eliminating 1 allocation per cache key
computation and 1 []byte(":") allocation.
197 lines
5.2 KiB
Go
197 lines
5.2 KiB
Go
// Package proxy 提供反向代理的核心功能,支持请求转发、负载均衡、健康检查等特性。
|
||
//
|
||
// 包含缓存处理器相关的结构体,用于配置代理缓存行为。
|
||
//
|
||
// 作者:xfy
|
||
package proxy
|
||
|
||
import (
|
||
"time"
|
||
|
||
"github.com/valyala/fasthttp"
|
||
"rua.plus/lolly/internal/cache"
|
||
"rua.plus/lolly/internal/loadbalance"
|
||
)
|
||
|
||
// buildCacheKeyHash 使用 FNV-64a 计算缓存键的 uint64 哈希值。
|
||
// 使用零分配方式构建哈希,避免 []byte(origKey) 转换。
|
||
func (p *Proxy) buildCacheKeyHash(ctx *fasthttp.RequestCtx) (uint64, string) {
|
||
method := ctx.Request.Header.Method()
|
||
uri := ctx.Request.URI().RequestURI()
|
||
|
||
var h uint64 = 14695981039346656037
|
||
for i := 0; i < len(method); i++ {
|
||
h ^= uint64(method[i])
|
||
h *= 1099511628211
|
||
}
|
||
h ^= uint64(':')
|
||
h *= 1099511628211
|
||
for i := 0; i < len(uri); i++ {
|
||
h ^= uint64(uri[i])
|
||
h *= 1099511628211
|
||
}
|
||
|
||
origKey := b2s(method) + ":" + b2s(uri)
|
||
return h, origKey
|
||
}
|
||
|
||
func (p *Proxy) buildCacheKeyHashValue(ctx *fasthttp.RequestCtx) uint64 {
|
||
method := ctx.Request.Header.Method()
|
||
uri := ctx.Request.URI().RequestURI()
|
||
|
||
var h uint64 = 14695981039346656037
|
||
for i := 0; i < len(method); i++ {
|
||
h ^= uint64(method[i])
|
||
h *= 1099511628211
|
||
}
|
||
h ^= uint64(':')
|
||
h *= 1099511628211
|
||
for i := 0; i < len(uri); i++ {
|
||
h ^= uint64(uri[i])
|
||
h *= 1099511628211
|
||
}
|
||
return h
|
||
}
|
||
|
||
// writeCachedResponse 将缓存的响应写入 FastHTTP 响应上下文。
|
||
//
|
||
// 设置响应体、状态码、响应头,并添加 X-Cache: HIT 头标记缓存命中。
|
||
//
|
||
// 参数:
|
||
// - ctx: FastHTTP 请求上下文
|
||
// - entry: 缓存条目,包含响应数据和元数据
|
||
func (p *Proxy) writeCachedResponse(ctx *fasthttp.RequestCtx, entry *cache.ProxyCacheEntry) {
|
||
ctx.Response.SetBody(entry.Data)
|
||
ctx.Response.SetStatusCode(entry.Status)
|
||
for key, value := range entry.Headers {
|
||
ctx.Response.Header.Set(key, value)
|
||
}
|
||
ctx.Response.Header.Set("X-Cache", "HIT")
|
||
}
|
||
|
||
// backgroundRefresh 在后台异步刷新缓存条目。
|
||
//
|
||
// 向对应的上游目标发送请求,获取最新响应并更新缓存。
|
||
// 该方法在独立 goroutine 中运行,不阻塞主请求流程。
|
||
//
|
||
// 参数:
|
||
// - req: 预复制的请求副本(调用方负责 Acquire/Release)
|
||
// - target: 要刷新的后端目标
|
||
// - hashKey: 缓存哈希键
|
||
// - origKey: 缓存原始键
|
||
func (p *Proxy) backgroundRefresh(req *fasthttp.Request, target *loadbalance.Target, hashKey uint64, origKey string) {
|
||
resp := fasthttp.AcquireResponse()
|
||
defer fasthttp.ReleaseResponse(resp)
|
||
|
||
// 如果启用 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)
|
||
if client == nil {
|
||
return
|
||
}
|
||
|
||
// 执行请求
|
||
err := client.Do(req, resp)
|
||
if err != nil {
|
||
p.cache.ReleaseLock(hashKey, err)
|
||
return
|
||
}
|
||
|
||
// 处理 304 Not Modified 响应
|
||
if resp.StatusCode() == 304 {
|
||
newHeaders := make(map[string]string, 5) // 预分配,通常只有 Last-Modified 和 ETag
|
||
if lm := resp.Header.Peek("Last-Modified"); len(lm) > 0 {
|
||
newHeaders["Last-Modified"] = b2s(lm)
|
||
}
|
||
if et := resp.Header.Peek("ETag"); len(et) > 0 {
|
||
newHeaders["ETag"] = b2s(et)
|
||
}
|
||
p.cache.RefreshTTL(hashKey, origKey, newHeaders)
|
||
return
|
||
}
|
||
|
||
// 提取响应头(使用 pool 复用 map)
|
||
headers, ok := headersPool.Get().(map[string]string)
|
||
if !ok {
|
||
headers = make(map[string]string, 20)
|
||
}
|
||
for k := range headers {
|
||
delete(headers, k)
|
||
}
|
||
for key, value := range resp.Header.All() {
|
||
headers[string(key)] = string(value)
|
||
}
|
||
|
||
// 更新缓存
|
||
p.cache.Set(hashKey, origKey, resp.Body(), headers, resp.StatusCode(), p.getCacheDuration(resp.StatusCode()))
|
||
}
|
||
|
||
// GetCache 返回代理的 ProxyCache 实例(用于 purge handler)。
|
||
// 如果缓存未启用,返回 nil。
|
||
func (p *Proxy) GetCache() *cache.ProxyCache {
|
||
return p.cache
|
||
}
|
||
|
||
// GetCacheStats 返回代理缓存的统计信息。
|
||
// 如果缓存未启用,返回 nil。
|
||
func (p *Proxy) GetCacheStats() *cache.ProxyCacheStats {
|
||
if p.cache == nil {
|
||
return nil
|
||
}
|
||
stats := p.cache.Stats()
|
||
return &stats
|
||
}
|
||
|
||
// getCacheDuration 根据状态码获取缓存时间。
|
||
// 优先级:CacheValid 配置 > MaxAge
|
||
//
|
||
// 映射规则:
|
||
// - 200-299: CacheValid.OK(0 时继承 MaxAge)
|
||
// - 301/302: CacheValid.Redirect
|
||
// - 404: CacheValid.NotFound
|
||
// - 400-499(除 404): CacheValid.ClientError
|
||
// - 500-599: CacheValid.ServerError
|
||
// - 其他: 不缓存(返回 0)
|
||
func (p *Proxy) getCacheDuration(statusCode int) time.Duration {
|
||
// 无 CacheValid 配置,使用 MaxAge
|
||
if p.config.CacheValid == nil {
|
||
return p.config.Cache.MaxAge
|
||
}
|
||
|
||
cv := p.config.CacheValid
|
||
|
||
switch {
|
||
case statusCode >= 200 && statusCode < 300:
|
||
if cv.OK > 0 {
|
||
return cv.OK
|
||
}
|
||
return p.config.Cache.MaxAge // 0 表示继承 MaxAge
|
||
|
||
case statusCode == 301 || statusCode == 302:
|
||
return cv.Redirect // 0 表示不缓存
|
||
|
||
case statusCode == 404:
|
||
return cv.NotFound
|
||
|
||
case statusCode >= 400 && statusCode < 500:
|
||
return cv.ClientError
|
||
|
||
case statusCode >= 500:
|
||
return cv.ServerError
|
||
|
||
default:
|
||
return 0 // 不缓存
|
||
}
|
||
}
|