This commit is contained in:
2026-09-10 13:49:36 -05:00
parent 7a5894d9cb
commit 768f44f669
8 changed files with 13805 additions and 14 deletions
+77
View File
@@ -0,0 +1,77 @@
package middleware
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"github.com/gofiber/fiber/v2"
)
// CacheStrategy defines caching profiles for different API data mutability tiers
type CacheStrategy string
const (
// Immutable: For historical completed games, past seasons, and shot charts.
// Edge TTL: 30 Days | Browser TTL: 1 Day
Immutable CacheStrategy = "immutable"
// SemiDynamic: For daily leaderboards, player season averages, and standings.
// Edge TTL: 1 Day | Browser TTL: 1 Minute | Stale-While-Revalidate: 10 Minutes
SemiDynamic CacheStrategy = "semi_dynamic"
// Realtime: For active game telemetry, live quarter stats, and fast-updating data.
// Edge TTL: 10 Seconds | Browser TTL: 5 Seconds | Stale-While-Revalidate: 30 Seconds
Realtime CacheStrategy = "realtime"
// NoCache: For transactional mutations, scrapers, and admin endpoints.
NoCache CacheStrategy = "no_cache"
)
// EdgeCache injects standardized Cloudflare & HTTP caching headers
func EdgeCache(strategy CacheStrategy, tags ...string) fiber.Handler {
return func(c *fiber.Ctx) error {
// Only cache idempotent read requests (GET / HEAD)
if c.Method() != fiber.MethodGet && c.Method() != fiber.MethodHead {
c.Set("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate")
c.Set("Pragma", "no-cache")
c.Set("Expires", "0")
return c.Next()
}
switch strategy {
case Immutable:
// Served directly by Cloudflare US Edge for 30 days without hitting Europe origin
c.Set("Cache-Control", "public, max-age=86400, s-maxage=2592000, immutable")
case SemiDynamic:
// Instant edge delivery; background revalidation over transatlantic backbone
c.Set("Cache-Control", "public, max-age=60, s-maxage=86400, stale-while-revalidate=600")
case Realtime:
c.Set("Cache-Control", "public, max-age=5, s-maxage=10, stale-while-revalidate=30")
case NoCache:
c.Set("Cache-Control", "no-store, no-cache, must-revalidate")
return c.Next()
}
// Cloudflare Cache-Tag header for instantaneous programmatic purge by entity
if len(tags) > 0 {
var tagHeader string
for i, tag := range tags {
if i > 0 {
tagHeader += ","
}
tagHeader += tag
}
c.Set("Cache-Tag", tagHeader)
}
c.Set("Vary", "Accept-Encoding, Origin")
return c.Next()
}
}
// ComputeETag generates a weak ETag based on response body bytes for 304 Not Modified optimization
func ComputeETag(data []byte) string {
hash := sha256.Sum256(data)
return fmt.Sprintf(`W/"%s"`, hex.EncodeToString(hash[:8]))
}