package middleware import ( "compress/gzip" "io" "net/http" "strconv" "strings" "time" ) type gzipResponseWriter struct { http.ResponseWriter io.Writer } func (w *gzipResponseWriter) Write(b []byte) (int, error) { return w.Writer.Write(b) } // Gzip compresses responses when the client accepts it. func Gzip(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { next.ServeHTTP(w, r) return } w.Header().Set("Vary", "Accept-Encoding") w.Header().Set("Content-Encoding", "gzip") gz := gzip.NewWriter(w) defer gz.Close() next.ServeHTTP(&gzipResponseWriter{ResponseWriter: w, Writer: gz}, r) }) } // CacheStatic wraps a file server with long-lived cache headers. func CacheStatic(dir string, maxAge time.Duration) http.Handler { fs := http.FileServer(http.Dir(dir)) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "public, max-age="+formatMaxAge(maxAge)+", immutable") fs.ServeHTTP(w, r) }) } // CacheImages wraps image serving; thumbs cache longer than originals. func CacheImages(dir string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rel := strings.TrimPrefix(r.URL.Path, "/") if strings.HasPrefix(rel, "thumbs/") || strings.HasPrefix(rel, "hero/") { w.Header().Set("Cache-Control", "public, max-age=604800, immutable") } else { w.Header().Set("Cache-Control", "public, max-age=86400") } http.FileServer(http.Dir(dir)).ServeHTTP(w, r) }) } func formatMaxAge(d time.Duration) string { return strconv.Itoa(int(d.Seconds())) }