mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 11:02:54 +00:00
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
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user