From 7ee875c544a0b31b9a85b1c409cd69fbff1db4e6 Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Fri, 24 Apr 2026 07:33:07 +0300 Subject: [PATCH] feat: Remove ETag generation and validation middleware, simplify CacheMiddleware to basic Cache-Control header - Remove ResponseWriterWrapper struct with Write/Body methods for response body capture - Remove ETagData/ETagGenerator structs and all ETag generation/validation/caching logic - Remove generateETag/generateContentETag/generateWeakETag/etagMatches helper functions - Remove ETagValidationMiddleware/ContentBasedETagMiddleware/CacheInvalidationMiddleware/AdvancedCacheMiddleware handlers --- .../internal/middleware/performance.go | 337 ------------------ 1 file changed, 337 deletions(-) diff --git a/apps/api-server/internal/middleware/performance.go b/apps/api-server/internal/middleware/performance.go index c25d5aa..236671b 100644 --- a/apps/api-server/internal/middleware/performance.go +++ b/apps/api-server/internal/middleware/performance.go @@ -1,35 +1,15 @@ package middleware import ( - "bytes" "context" - "crypto/sha256" "fmt" "net/http" - "strings" "sync" "time" "github.com/gin-gonic/gin" ) -// ResponseWriterWrapper wraps gin.ResponseWriter to capture response body -type ResponseWriterWrapper struct { - gin.ResponseWriter - body *bytes.Buffer -} - -// Write captures the response body while writing to the original writer -func (w *ResponseWriterWrapper) Write(data []byte) (int, error) { - w.body.Write(data) - return w.ResponseWriter.Write(data) -} - -// Body returns the captured response body -func (w *ResponseWriterWrapper) Body() []byte { - return w.body.Bytes() -} - // PerformanceMetrics tracks performance statistics type PerformanceMetrics struct { mu sync.RWMutex @@ -250,328 +230,11 @@ func CacheMiddleware(duration time.Duration) gin.HandlerFunc { return func(c *gin.Context) { if c.Request.Method == "GET" { c.Header("Cache-Control", "public, max-age="+duration.String()) - c.Header("ETag", generateETag(c)) } c.Next() } } -// ETagData represents the components used for ETag generation -type ETagData struct { - Path string - Method string - Content []byte - Timestamp time.Time - UserID string - Params string -} - -// ETagGenerator handles sophisticated ETag generation -type ETagGenerator struct { - secretKey []byte - cacheStorage sync.Map // For storing computed ETags -} - -// NewETagGenerator creates a new ETag generator instance -func NewETagGenerator(secretKey string) *ETagGenerator { - return &ETagGenerator{ - secretKey: []byte(secretKey), - } -} - -// GenerateETag creates a sophisticated ETag based on multiple factors -func (g *ETagGenerator) GenerateETag(data ETagData) string { - // Create a composite hash from multiple factors - h := sha256.New() - - // Include path and method - h.Write([]byte(data.Path)) - h.Write([]byte(data.Method)) - - // Include content if available - if len(data.Content) > 0 { - h.Write(data.Content) - } - - // Include timestamp (rounded to second for consistency) - timestampBytes := []byte(fmt.Sprintf("%d", data.Timestamp.Unix())) - h.Write(timestampBytes) - - // Include user context for personalized responses - if data.UserID != "" { - h.Write([]byte(data.UserID)) - } - - // Include query parameters for cache differentiation - if data.Params != "" { - h.Write([]byte(data.Params)) - } - - // Add secret key for security - h.Write(g.secretKey) - - // Generate final ETag - hash := h.Sum(nil) - return fmt.Sprintf("\"%x\"", hash) -} - -// GenerateWeakETag creates a weak ETag for dynamic content -func (g *ETagGenerator) GenerateWeakETag(data ETagData) string { - etag := g.GenerateETag(data) - return "W/" + etag -} - -// GetCachedETag retrieves a cached ETag if available -func (g *ETagGenerator) GetCachedETag(key string) (string, bool) { - if cached, ok := g.cacheStorage.Load(key); ok { - if etag, ok := cached.(string); ok { - return etag, true - } - } - return "", false -} - -// CacheETag stores an ETag in the cache -func (g *ETagGenerator) CacheETag(key, etag string) { - g.cacheStorage.Store(key, etag) -} - -// InvalidateCache removes ETag entries matching a pattern -func (g *ETagGenerator) InvalidateCache(pattern string) { - g.cacheStorage.Range(func(key, value interface{}) bool { - if keyStr, ok := key.(string); ok { - if strings.Contains(keyStr, pattern) { - g.cacheStorage.Delete(key) - } - } - return true - }) -} - -// Global ETag generator instance -var etagGenerator = NewETagGenerator("telecom-platform-etag-secret") - -// generateETag generates a sophisticated ETag for caching -func generateETag(c *gin.Context) string { - // Extract relevant data for ETag generation - data := ETagData{ - Path: c.Request.URL.Path, - Method: c.Request.Method, - Timestamp: time.Now(), - Params: c.Request.URL.RawQuery, - } - - // Add user context if available - if userID, exists := c.Get("user_id"); exists { - if idStr, ok := userID.(string); ok { - data.UserID = idStr - } - } - - // Try to get cached ETag first - cacheKey := fmt.Sprintf("%s:%s:%s", data.Method, data.Path, data.Params) - if cached, exists := etagGenerator.GetCachedETag(cacheKey); exists { - return cached - } - - // Generate new ETag - etag := etagGenerator.GenerateETag(data) - - // Cache the ETag for future use - etagGenerator.CacheETag(cacheKey, etag) - - return etag -} - -// generateContentETag creates an ETag based on response content -func generateContentETag(c *gin.Context, content []byte) string { - data := ETagData{ - Path: c.Request.URL.Path, - Method: c.Request.Method, - Content: content, - Timestamp: time.Now(), - Params: c.Request.URL.RawQuery, - } - - // Add user context if available - if userID, exists := c.Get("user_id"); exists { - if idStr, ok := userID.(string); ok { - data.UserID = idStr - } - } - - return etagGenerator.GenerateETag(data) -} - -// generateWeakETag creates a weak ETag for dynamic content -func generateWeakETag(c *gin.Context) string { - data := ETagData{ - Path: c.Request.URL.Path, - Method: c.Request.Method, - Timestamp: time.Now(), - Params: c.Request.URL.RawQuery, - } - - // Add user context if available - if userID, exists := c.Get("user_id"); exists { - if idStr, ok := userID.(string); ok { - data.UserID = idStr - } - } - - return etagGenerator.GenerateWeakETag(data) -} - -// ETagValidationMiddleware handles ETag validation for conditional requests -func ETagValidationMiddleware() gin.HandlerFunc { - return func(c *gin.Context) { - // Only handle GET and HEAD requests for ETag validation - if c.Request.Method != "GET" && c.Request.Method != "HEAD" { - c.Next() - return - } - - // Check If-None-Match header (for GET requests) - if noneMatch := c.GetHeader("If-None-Match"); noneMatch != "" { - // Generate current ETag - currentETag := generateETag(c) - - // Check if any of the ETags match - if etagMatches(noneMatch, currentETag) { - c.Header("ETag", currentETag) - c.Status(http.StatusNotModified) - c.Abort() - return - } - } - - // Check If-Match header (for PUT/PATCH/DELETE requests) - if match := c.GetHeader("If-Match"); match != "" { - currentETag := generateETag(c) - - if !etagMatches(match, currentETag) { - c.Status(http.StatusPreconditionFailed) - c.Abort() - return - } - } - - c.Next() - } -} - -// ContentBasedETagMiddleware generates ETags based on response content -func ContentBasedETagMiddleware() gin.HandlerFunc { - return func(c *gin.Context) { - // Wrap the response writer to capture the body - wrapper := &ResponseWriterWrapper{ - ResponseWriter: c.Writer, - body: bytes.NewBuffer(nil), - } - c.Writer = wrapper - - c.Next() - - // Only generate ETags for successful GET requests - if c.Request.Method != "GET" || c.Writer.Status() != http.StatusOK { - return - } - - // Generate ETag based on captured response content - responseBody := wrapper.Body() - if len(responseBody) > 0 { - etag := generateContentETag(c, responseBody) - c.Header("ETag", etag) - } else { - // Fallback to weak ETag for empty responses - etag := generateWeakETag(c) - c.Header("ETag", etag) - } - } -} - -// CacheInvalidationMiddleware handles cache invalidation for modified resources -func CacheInvalidationMiddleware() gin.HandlerFunc { - return func(c *gin.Context) { - // Store original request for post-processing - c.Next() - - // Invalidate cache for POST, PUT, PATCH, DELETE requests - if c.Request.Method == "POST" || c.Request.Method == "PUT" || - c.Request.Method == "PATCH" || c.Request.Method == "DELETE" { - - // Invalidate ETags for this resource pattern - pattern := c.Request.URL.Path - etagGenerator.InvalidateCache(pattern) - - // Also invalidate related collection resources - if strings.Contains(pattern, "/") { - parts := strings.Split(strings.Trim(pattern, "/"), "/") - if len(parts) > 0 { - collectionPattern := "/" + parts[0] - etagGenerator.InvalidateCache(collectionPattern) - } - } - } - } -} - -// etagMatches checks if any of the provided ETags match the current ETag -func etagMatches(headerValue, currentETag string) bool { - // Handle wildcard - if headerValue == "*" { - return true - } - - // Parse the header value (can contain multiple ETags) - etags := strings.Split(headerValue, ",") - for _, etag := range etags { - etag = strings.TrimSpace(etag) - // Remove quotes for comparison - etag = strings.Trim(etag, "\"") - current := strings.Trim(currentETag, "\"W/") - - if etag == current || etag == "W/"+current { - return true - } - } - - return false -} - -// AdvancedCacheMiddleware provides sophisticated caching with ETags -func AdvancedCacheMiddleware(maxAge time.Duration, useWeakETag bool) gin.HandlerFunc { - return func(c *gin.Context) { - if c.Request.Method == "GET" || c.Request.Method == "HEAD" { - // Set cache control headers - cacheControl := fmt.Sprintf("public, max-age=%d", int(maxAge.Seconds())) - if useWeakETag { - cacheControl += ", must-revalidate" - } - c.Header("Cache-Control", cacheControl) - - // Generate appropriate ETag - var etag string - if useWeakETag { - etag = generateWeakETag(c) - } else { - etag = generateETag(c) - } - c.Header("ETag", etag) - - // Add Vary header for proper caching with different user contexts - if userID, exists := c.Get("user_id"); exists { - c.Header("Vary", "Accept, Authorization") - if userIDStr, ok := userID.(string); ok && userIDStr != "" { - c.Header("X-User-Context", userIDStr) - } - } - } - - c.Next() - } -} - // TimeoutMiddleware adds timeout to requests func TimeoutMiddleware(timeout time.Duration) gin.HandlerFunc { return func(c *gin.Context) {