package middleware import ( "strconv" "strings" "github.com/gin-gonic/gin" ) // CacheHeadersConfig defines cache-control rules per path prefix type CacheHeadersConfig struct { Rules []CacheRule } // CacheRule maps a URL path prefix to cache headers type CacheRule struct { PathPrefix string MaxAge int // seconds Directive string // e.g. "public", "private", "no-cache" Immutable bool } // DefaultCacheHeadersConfig returns production-ready cache header rules // Reference: ORIGIN_PERFORMANCE_TARGETS.md §8.4 func DefaultCacheHeadersConfig() CacheHeadersConfig { return CacheHeadersConfig{ Rules: []CacheRule{ // Static assets (JS, CSS, fonts) — immutable with content hash {PathPrefix: "/static/", MaxAge: 31536000, Directive: "public", Immutable: true}, {PathPrefix: "/assets/", MaxAge: 31536000, Directive: "public", Immutable: true}, // Audio files — CDN cached for 7 days {PathPrefix: "/audio/", MaxAge: 604800, Directive: "public"}, // HLS segments — short cache (live content changes) {PathPrefix: "/hls/", MaxAge: 60, Directive: "public"}, // Images (covers, avatars) — CDN cached for 30 days {PathPrefix: "/images/", MaxAge: 2592000, Directive: "public"}, {PathPrefix: "/uploads/", MaxAge: 86400, Directive: "public"}, // API responses — no caching by default (handled by ResponseCache middleware) {PathPrefix: "/api/", MaxAge: 0, Directive: "no-cache"}, }, } } // CacheHeaders returns a middleware that sets appropriate Cache-Control headers // based on the request path. This enables CDN and browser caching. func CacheHeaders(cfg CacheHeadersConfig) gin.HandlerFunc { return func(c *gin.Context) { path := c.Request.URL.Path for _, rule := range cfg.Rules { if strings.HasPrefix(path, rule.PathPrefix) { setCacheHeaders(c, rule) break } } c.Next() } } func setCacheHeaders(c *gin.Context, rule CacheRule) { if rule.MaxAge == 0 && rule.Directive == "no-cache" { c.Header("Cache-Control", "no-cache, no-store, must-revalidate") c.Header("Pragma", "no-cache") return } var parts []string if rule.Directive != "" { parts = append(parts, rule.Directive) } if rule.MaxAge > 0 { parts = append(parts, "max-age="+strconv.Itoa(rule.MaxAge)) } if rule.Immutable { parts = append(parts, "immutable") } if len(parts) > 0 { c.Header("Cache-Control", strings.Join(parts, ", ")) } }