3x-ui/web/middleware/cache.go

133 lines
3.6 KiB
Go
Raw Normal View History

2026-01-11 21:57:04 +00:00
// Package middleware provides HTTP response caching middleware for the 3x-ui web panel.
package middleware
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"time"
"github.com/gin-gonic/gin"
"github.com/mhsanaei/3x-ui/v2/web/cache"
)
// CacheMiddleware creates a middleware that caches HTTP responses.
// It caches GET requests based on the full URL path and query parameters.
func CacheMiddleware(ttl time.Duration) gin.HandlerFunc {
return func(c *gin.Context) {
// Only cache GET requests
if c.Request.Method != "GET" {
c.Next()
return
}
// Generate cache key from request path and query
cacheKey := generateCacheKey(c.Request.URL.Path, c.Request.URL.RawQuery)
// Try to get from cache
var cachedResponse map[string]interface{}
err := cache.GetJSON(cacheKey, &cachedResponse)
if err == nil {
// Cache hit - return cached response
c.JSON(200, cachedResponse)
c.Abort()
return
}
// Cache miss - continue to handler and capture response
c.Next()
// Only cache successful responses (status 200)
if c.Writer.Status() == 200 {
// Try to capture the response body
// Note: This is a simplified version - in production you might want to use
// a response writer wrapper to capture the actual response body
// For now, we'll let the service layer handle caching
}
}
}
// CacheResponse caches a JSON response with the given key and TTL.
func CacheResponse(key string, data interface{}, ttl time.Duration) error {
return cache.SetJSON(key, data, ttl)
}
// GetCachedResponse retrieves a cached JSON response.
func GetCachedResponse(key string, dest interface{}) error {
return cache.GetJSON(key, dest)
}
// InvalidateCacheKey invalidates a specific cache key.
func InvalidateCacheKey(key string) error {
return cache.Delete(key)
}
// generateCacheKey creates a cache key from path and query string.
func generateCacheKey(path, query string) string {
key := fmt.Sprintf("http:%s", path)
if query != "" {
hash := sha256.Sum256([]byte(query))
key += ":" + hex.EncodeToString(hash[:])[:16]
}
return key
}
// UserCacheMiddleware creates a middleware that caches responses per user.
// It includes the user ID in the cache key to ensure user-specific caching.
func UserCacheMiddleware(ttl time.Duration) gin.HandlerFunc {
return func(c *gin.Context) {
// Only cache GET requests
if c.Request.Method != "GET" {
c.Next()
return
}
// Get user ID from session
userID := getUserIDFromContext(c)
if userID == 0 {
c.Next()
return
}
// Generate cache key with user ID
cacheKey := generateUserCacheKey(c.Request.URL.Path, c.Request.URL.RawQuery, userID)
// Try to get from cache
var cachedResponse map[string]interface{}
err := cache.GetJSON(cacheKey, &cachedResponse)
if err == nil {
// Cache hit - return cached response
c.JSON(200, cachedResponse)
c.Abort()
return
}
// Cache miss - continue to handler
c.Next()
}
}
// generateUserCacheKey creates a cache key with user ID.
func generateUserCacheKey(path, query string, userID int) string {
key := fmt.Sprintf("http:user:%d:%s", userID, path)
if query != "" {
hash := sha256.Sum256([]byte(query))
key += ":" + hex.EncodeToString(hash[:])[:16]
}
return key
}
// getUserIDFromContext extracts user ID from gin context.
// This is a helper function - you may need to adjust based on your session implementation.
func getUserIDFromContext(c *gin.Context) int {
// Try to get from session
if user, exists := c.Get("user"); exists {
if userMap, ok := user.(map[string]interface{}); ok {
if id, ok := userMap["id"].(int); ok {
return id
}
}
}
return 0
}