Files
AIRouter/internal/server/idempotency.go
T
selfrelease da9c8334d8
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled
CI / security-scan (push) Has been cancelled
feat: 十轮网关优化 - 安全加固/可观测性/性能/可靠性
- SSE Keepalive Ping (15s心跳防止代理断连)
- Timing HTTP 头 (X-Timing-Queue/Inference/Total-Ms)
- Adapter Request-ID 传播到后端
- Session 清理日志回调
- Server 安全加固 (ReadHeaderTimeout/MaxHeaderBytes 防 slowloris)
- Usage Tracker 数据保留清理 (retentionDays + 定期清理)
- Config Reload 后 Adapter Registry 更新 (RegisterIfAbsent + RWMutex)
- Rate Limiter 空闲 Bucket 清理 (30分钟过期)
- Shutdown Drain 超时可配置 (ShutdownDrainSeconds)
- Config 模型字段校验增强 (provider/endpoint/actual_model)
- Auth 过期 Key 自动清理 (5分钟扫描)
- Admin API Rate Limiting
- Adapter Health Check 独立超时 (每个 adapter 3s)
- TCP 连接阶段超时 (DialContext 5s + KeepAlive 30s)
- 幂等键缓存、审计日志、Gzip 中间件、CORS Expose Headers
- Backpressure 响应头、熔断器 Prometheus 指标
- 连接池优化、Trace-ID 全链路传播
2026-08-03 15:43:11 +08:00

148 lines
3.5 KiB
Go

package server
import (
"net/http"
"time"
"github.com/edgeai/gateway/internal/handler"
)
// idempotencyEntry 幂等键缓存条目。
// 记录请求处理状态和响应,用于在重复请求时返回缓存结果。
type idempotencyEntry struct {
status int
response []byte
createdAt time.Time
inFlight bool // 正在处理中
}
const (
// idempotencyTTL 幂等键缓存存活时间。
idempotencyTTL = 10 * time.Minute
// idempotencyCleanupInterval 清理间隔。
idempotencyCleanupInterval = 5 * time.Minute
)
// checkIdempotency 检查幂等键,如果重复请求则返回缓存的响应。
// 如果是首次请求,返回 nil 表示可以继续处理。
// 如果是正在处理中的重复请求,返回 409 Conflict。
func (s *Server) checkIdempotency(w http.ResponseWriter, key, appID string) bool {
if key == "" {
return false
}
cacheKey := appID + ":" + key
s.idempotencyMu.Lock()
entry, exists := s.idempotencyCache[cacheKey]
if exists {
if entry.inFlight {
// 正在处理中,返回 409
s.idempotencyMu.Unlock()
handler.WriteError(w, handler.NewGatewayError(handler.ErrInvalidRequest, "duplicate idempotency key, request already in progress"))
return true
}
// 检查是否过期
if time.Since(entry.createdAt) > idempotencyTTL {
delete(s.idempotencyCache, cacheKey)
} else {
// 返回缓存的响应
s.idempotencyMu.Unlock()
w.Header().Set("Content-Type", "application/json")
w.Header().Set("X-Idempotent-Replay", "true")
w.WriteHeader(entry.status)
w.Write(entry.response)
return true
}
}
// 标记为正在处理中
s.idempotencyCache[cacheKey] = &idempotencyEntry{
inFlight: true,
createdAt: time.Now(),
}
s.idempotencyMu.Unlock()
return false
}
// storeIdempotencyResult 存储幂等请求的响应结果。
func (s *Server) storeIdempotencyResult(key, appID string, status int, response []byte) {
if key == "" {
return
}
cacheKey := appID + ":" + key
s.idempotencyMu.Lock()
s.idempotencyCache[cacheKey] = &idempotencyEntry{
status: status,
response: response,
createdAt: time.Now(),
inFlight: false,
}
s.idempotencyMu.Unlock()
}
// cleanupIdempotencyCache 清理过期的幂等键缓存。
func (s *Server) cleanupIdempotencyCache() {
s.idempotencyMu.Lock()
defer s.idempotencyMu.Unlock()
now := time.Now()
for k, entry := range s.idempotencyCache {
if now.Sub(entry.createdAt) > idempotencyTTL {
delete(s.idempotencyCache, k)
}
}
}
// startIdempotencyCleanup 启动后台清理任务,定期清理过期的幂等键。
// 返回停止函数。
func (s *Server) startIdempotencyCleanup() func() {
ticker := time.NewTicker(idempotencyCleanupInterval)
stopCh := make(chan struct{})
go func() {
for {
select {
case <-ticker.C:
s.cleanupIdempotencyCache()
case <-stopCh:
ticker.Stop()
return
}
}
}()
return func() {
close(stopCh)
}
}
// captureResponseWriter 捕获响应状态和响应体,用于幂等性缓存。
type captureResponseWriter struct {
http.ResponseWriter
statusCode int
body []byte
}
func (crw *captureResponseWriter) WriteHeader(code int) {
crw.statusCode = code
crw.ResponseWriter.WriteHeader(code)
}
func (crw *captureResponseWriter) Write(b []byte) (int, error) {
if crw.statusCode == 0 {
crw.statusCode = 200
}
crw.body = append(crw.body, b...)
return crw.ResponseWriter.Write(b)
}
func (crw *captureResponseWriter) Flush() {
if f, ok := crw.ResponseWriter.(http.Flusher); ok {
f.Flush()
}
}