Improve caching
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// assetVersions maps each embedded asset (path relative to assets/, e.g.
|
||||
// "page/toc.js") to a short hash of its content. Templates reference assets
|
||||
// through assetURL, which appends the hash as ?v=…, so a versioned URL names
|
||||
// one exact file content and can be cached forever; a rebuilt asset gets a
|
||||
// new URL.
|
||||
var assetVersions = hashAssets()
|
||||
|
||||
func hashAssets() map[string]string {
|
||||
versions := map[string]string{}
|
||||
_ = fs.WalkDir(assets, "assets", func(p string, d fs.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() {
|
||||
return err
|
||||
}
|
||||
b, err := assets.ReadFile(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sum := sha256.Sum256(b)
|
||||
versions[strings.TrimPrefix(p, "assets/")] = hex.EncodeToString(sum[:])[:12]
|
||||
return nil
|
||||
})
|
||||
return versions
|
||||
}
|
||||
|
||||
// assetURL is the "asset" template func: {{asset "style.css"}} renders
|
||||
// /_/style.css?v=<hash>. An unknown name fails the template so a typo or a
|
||||
// removed file surfaces as a render error instead of a silent 404.
|
||||
func assetURL(name string) (string, error) {
|
||||
v, ok := assetVersions[name]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unknown asset %q", name)
|
||||
}
|
||||
return "/_/" + name + "?v=" + v, nil
|
||||
}
|
||||
|
||||
// assetFuncs is for the standalone fragment templates that do not take the
|
||||
// full tmplFuncs.
|
||||
var assetFuncs = template.FuncMap{"asset": assetURL}
|
||||
|
||||
// serveStatic serves the embedded assets under /_/. Embedded files carry no
|
||||
// modtime, so http.FileServer alone sends no validators and browsers refetch
|
||||
// every asset on every page. Instead:
|
||||
// - a URL carrying the current ?v= hash is immutable;
|
||||
// - fonts are immutable too (referenced unversioned from style.css, never
|
||||
// change);
|
||||
// - anything else (unversioned or stale ?v=, e.g. icons from CSS) gets the
|
||||
// content hash as ETag and revalidates, answered with 304 when unchanged.
|
||||
func serveStatic() http.Handler {
|
||||
staticFS, _ := fs.Sub(assets, "assets")
|
||||
static := http.StripPrefix("/_/", http.FileServer(http.FS(staticFS)))
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
name := strings.TrimPrefix(r.URL.Path, "/_/")
|
||||
v, ok := assetVersions[name]
|
||||
switch {
|
||||
case ok && r.URL.Query().Get("v") == v, strings.HasPrefix(name, "fonts/"):
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
|
||||
case ok:
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("ETag", `"`+v+`"`)
|
||||
}
|
||||
static.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user