render.go (1540B)
1 package handlers 2 3 import ( 4 "crypto/sha256" 5 "fmt" 6 "net/http" 7 "os" 8 "strings" 9 10 "git.prime8.dev/moncheeta/prime8dev/internal/web" 11 ) 12 13 func (h *Handler) renderTemplate(w http.ResponseWriter, r *http.Request, name string, data web.Data, maxAgeSeconds int) { 14 if maxAgeSeconds < 0 { 15 maxAgeSeconds = 0 16 } 17 18 data.Path = r.URL.Path 19 body, err := h.renderer.Render(name, data) 20 if err != nil { 21 http.Error(w, "Failed to render content", http.StatusInternalServerError) 22 return 23 } 24 25 sum := sha256.Sum256([]byte(body)) 26 etag := fmt.Sprintf(`W/"%x"`, sum) 27 cacheEnabled := shouldCacheResponses() 28 29 w.Header().Set("Content-Type", "text/html; charset=utf-8") 30 if !cacheEnabled || maxAgeSeconds == 0 { 31 applyNoCacheHeaders(w) 32 } else if maxAgeSeconds > 0 { 33 // revalidate every time (cheap 304 via ETag) so a deploy shows up at once; 34 // maxAgeSeconds now only gates whether caching/ETags apply at all. 35 w.Header().Set("Cache-Control", "public, no-cache") 36 w.Header().Set("ETag", etag) 37 } 38 w.Header().Set("Vary", "HX-Request") 39 40 if cacheEnabled && maxAgeSeconds > 0 && strings.Contains(r.Header.Get("If-None-Match"), etag) { 41 w.WriteHeader(http.StatusNotModified) 42 return 43 } 44 45 _, _ = w.Write([]byte(body)) 46 } 47 48 func shouldCacheResponses() bool { 49 mode := strings.ToLower(strings.TrimSpace(os.Getenv("APP_ENV"))) 50 return mode == "prod" 51 } 52 53 func applyNoCacheHeaders(w http.ResponseWriter) { 54 w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0") 55 w.Header().Set("Pragma", "no-cache") 56 w.Header().Set("Expires", "0") 57 }