docker匿名Token缓存
This commit is contained in:
@@ -47,6 +47,12 @@ type AppConfig struct {
|
|||||||
|
|
||||||
// 新增:Registry映射配置
|
// 新增:Registry映射配置
|
||||||
Registries map[string]RegistryMapping `toml:"registries"`
|
Registries map[string]RegistryMapping `toml:"registries"`
|
||||||
|
|
||||||
|
// Token缓存配置
|
||||||
|
TokenCache struct {
|
||||||
|
Enabled bool `toml:"enabled"` // 是否启用token缓存
|
||||||
|
DefaultTTL string `toml:"defaultTTL"` // 默认缓存时间
|
||||||
|
} `toml:"tokenCache"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -118,6 +124,13 @@ func DefaultConfig() *AppConfig {
|
|||||||
Enabled: true,
|
Enabled: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
TokenCache: struct {
|
||||||
|
Enabled bool `toml:"enabled"`
|
||||||
|
DefaultTTL string `toml:"defaultTTL"`
|
||||||
|
}{
|
||||||
|
Enabled: true, // docker认证的匿名Token缓存配置,用于提升性能
|
||||||
|
DefaultTTL: "20m",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,3 +81,10 @@ enabled = true
|
|||||||
# authHost = "harbor.company.com/service/token"
|
# authHost = "harbor.company.com/service/token"
|
||||||
# authType = "basic"
|
# authType = "basic"
|
||||||
# enabled = false
|
# enabled = false
|
||||||
|
|
||||||
|
# docker的匿名Token缓存配置,显著提升性能
|
||||||
|
[tokenCache]
|
||||||
|
# 是否启用token缓存
|
||||||
|
enabled = true
|
||||||
|
# 默认缓存时间(自动从响应中提取实际TTL)
|
||||||
|
defaultTTL = "20m"
|
||||||
|
|||||||
@@ -305,8 +305,70 @@ func handleTagsRequest(c *gin.Context, imageRef string) {
|
|||||||
c.JSON(http.StatusOK, response)
|
c.JSON(http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProxyDockerAuthGin Docker认证代理
|
// ProxyDockerAuthGin Docker认证代理(带缓存优化)
|
||||||
func ProxyDockerAuthGin(c *gin.Context) {
|
func ProxyDockerAuthGin(c *gin.Context) {
|
||||||
|
// 检查是否启用token缓存
|
||||||
|
if isTokenCacheEnabled() {
|
||||||
|
proxyDockerAuthWithCache(c)
|
||||||
|
} else {
|
||||||
|
proxyDockerAuthOriginal(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// proxyDockerAuthWithCache 带缓存的认证代理
|
||||||
|
func proxyDockerAuthWithCache(c *gin.Context) {
|
||||||
|
// 1. 构建缓存key(基于完整的查询参数)
|
||||||
|
cacheKey := buildCacheKey(c.Request.URL.RawQuery)
|
||||||
|
|
||||||
|
// 2. 尝试从缓存获取token
|
||||||
|
if cachedToken := globalTokenCache.Get(cacheKey); cachedToken != "" {
|
||||||
|
writeTokenResponse(c, cachedToken)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 缓存未命中,创建响应记录器
|
||||||
|
recorder := &ResponseRecorder{
|
||||||
|
ResponseWriter: c.Writer,
|
||||||
|
statusCode: 200,
|
||||||
|
}
|
||||||
|
c.Writer = recorder
|
||||||
|
|
||||||
|
// 4. 调用原有认证逻辑
|
||||||
|
proxyDockerAuthOriginal(c)
|
||||||
|
|
||||||
|
// 5. 如果认证成功,缓存响应
|
||||||
|
if recorder.statusCode == 200 && len(recorder.body) > 0 {
|
||||||
|
ttl := extractTTLFromResponse(recorder.body)
|
||||||
|
globalTokenCache.Set(cacheKey, string(recorder.body), ttl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 写入实际响应(如果还没写入)
|
||||||
|
if !recorder.written {
|
||||||
|
c.Writer = recorder.ResponseWriter
|
||||||
|
c.Data(recorder.statusCode, "application/json", recorder.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseRecorder HTTP响应记录器
|
||||||
|
type ResponseRecorder struct {
|
||||||
|
gin.ResponseWriter
|
||||||
|
statusCode int
|
||||||
|
body []byte
|
||||||
|
written bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ResponseRecorder) WriteHeader(code int) {
|
||||||
|
r.statusCode = code
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ResponseRecorder) Write(data []byte) (int, error) {
|
||||||
|
r.body = append(r.body, data...)
|
||||||
|
r.written = true
|
||||||
|
return r.ResponseWriter.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// proxyDockerAuthOriginal Docker认证代理(原始逻辑,保持不变)
|
||||||
|
func proxyDockerAuthOriginal(c *gin.Context) {
|
||||||
// 检查是否有目标Registry域名(来自Context)
|
// 检查是否有目标Registry域名(来自Context)
|
||||||
var authURL string
|
var authURL string
|
||||||
if targetDomain, exists := c.Get("target_registry_domain"); exists {
|
if targetDomain, exists := c.Get("target_registry_domain"); exists {
|
||||||
|
|||||||
83
src/token_cache.go
Normal file
83
src/token_cache.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CachedToken 缓存的Token信息
|
||||||
|
type CachedToken struct {
|
||||||
|
Token string
|
||||||
|
ExpiresAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleTokenCache 极简Token缓存
|
||||||
|
type SimpleTokenCache struct {
|
||||||
|
cache sync.Map // 线程安全的并发映射
|
||||||
|
}
|
||||||
|
|
||||||
|
var globalTokenCache = &SimpleTokenCache{}
|
||||||
|
|
||||||
|
// Get 获取缓存的token,如果不存在或过期返回空字符串
|
||||||
|
func (c *SimpleTokenCache) Get(key string) string {
|
||||||
|
if v, ok := c.cache.Load(key); ok {
|
||||||
|
if cached := v.(*CachedToken); time.Now().Before(cached.ExpiresAt) {
|
||||||
|
return cached.Token
|
||||||
|
}
|
||||||
|
// 自动清理过期token,保持内存整洁
|
||||||
|
c.cache.Delete(key)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set 设置token缓存,自动计算过期时间
|
||||||
|
func (c *SimpleTokenCache) Set(key, token string, ttl time.Duration) {
|
||||||
|
c.cache.Store(key, &CachedToken{
|
||||||
|
Token: token,
|
||||||
|
ExpiresAt: time.Now().Add(ttl),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildCacheKey 构建稳定的缓存key
|
||||||
|
func buildCacheKey(query string) string {
|
||||||
|
// 使用MD5确保key的一致性和简洁性
|
||||||
|
return fmt.Sprintf("token:%x", md5.Sum([]byte(query)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractTTLFromResponse 从响应中智能提取TTL
|
||||||
|
func extractTTLFromResponse(responseBody []byte) time.Duration {
|
||||||
|
var tokenResp struct {
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认30分钟TTL,确保稳定性
|
||||||
|
defaultTTL := 30 * time.Minute
|
||||||
|
|
||||||
|
if json.Unmarshal(responseBody, &tokenResp) == nil && tokenResp.ExpiresIn > 0 {
|
||||||
|
// 使用响应中的过期时间,但提前5分钟过期确保安全边际
|
||||||
|
safeTTL := time.Duration(tokenResp.ExpiresIn-300) * time.Second
|
||||||
|
if safeTTL > 5*time.Minute { // 确保至少有5分钟的缓存时间
|
||||||
|
return safeTTL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultTTL
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeTokenResponse 写入token响应
|
||||||
|
func writeTokenResponse(c *gin.Context, cachedBody string) {
|
||||||
|
// 直接返回缓存的完整响应体,保持格式一致性
|
||||||
|
c.Header("Content-Type", "application/json")
|
||||||
|
c.String(200, cachedBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTokenCacheEnabled 检查token缓存是否启用
|
||||||
|
func isTokenCacheEnabled() bool {
|
||||||
|
cfg := GetConfig()
|
||||||
|
return cfg.TokenCache.Enabled
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user