Send our own Cache-Control headers instead of relying entirely on front-proxy configuration, so browsers cache appropriately even when Zampler is reached directly. --static-max-age, --file-max-age, and --api-max-age (all cobra Duration flags, 0 disables caching) cover the three response categories independently; --no-immutable turns off the immutable directive on served files, which is on by default since a file's content never changes for a given ID. The HTML shell always gets no-cache regardless of --static-max-age, since it isn't content-hashed like the rest of the Vite build and must keep revalidating so a new deploy's asset references are picked up. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
91 lines
2.6 KiB
Go
91 lines
2.6 KiB
Go
package server
|
|
|
|
import (
|
|
"embed"
|
|
"io/fs"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"code.aneur.in/zampler/zampler/internal/database"
|
|
"code.aneur.in/zampler/zampler/internal/dto"
|
|
"code.aneur.in/zampler/zampler/internal/rest"
|
|
"code.aneur.in/zampler/zampler/internal/server/middlewares"
|
|
"github.com/gorilla/mux"
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
//go:embed static
|
|
var staticFiles embed.FS
|
|
|
|
// htmlCacheControl is fixed rather than driven by --static-max-age: it
|
|
// covers index.html, which is not content-hashed (unlike the Vite build's
|
|
// /assets/* files) and must always be revalidated so a deploy's new,
|
|
// hashed asset references are picked up promptly.
|
|
const htmlCacheControl = "no-cache"
|
|
|
|
type Server struct {
|
|
url string
|
|
|
|
allowIndexing bool
|
|
|
|
db *database.DB
|
|
log zerolog.Logger
|
|
|
|
static http.Handler
|
|
staticFiles fs.FS
|
|
|
|
fileCache *middlewares.CacheControl
|
|
}
|
|
|
|
func (srv *Server) ConfigureRouter(r *mux.Router) {
|
|
r.Path("/config.json").Methods(http.MethodGet, http.MethodOptions).HandlerFunc(srv.GetConfig)
|
|
r.Path("/robots.txt").Methods(http.MethodGet, http.MethodOptions).HandlerFunc(srv.GetRobots)
|
|
r.Path("/file/{id}").Methods(http.MethodGet, http.MethodOptions).Handler(srv.fileCache.Middleware(http.HandlerFunc(srv.ReadFile)))
|
|
}
|
|
|
|
func (srv *Server) ConfigureStaticAssets(r *mux.Router, maxAge int) {
|
|
staticFiles, _ := fs.Sub(staticFiles, "static")
|
|
|
|
srv.staticFiles = staticFiles
|
|
srv.static = http.FileServerFS(staticFiles)
|
|
|
|
assetHandler := middlewares.NewCacheControl(maxAge, false).Middleware(srv.static)
|
|
|
|
r.Methods(http.MethodGet, http.MethodOptions).Handler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
if isHTMLPath(req.URL.Path) {
|
|
w.Header().Set("Cache-Control", htmlCacheControl)
|
|
srv.static.ServeHTTP(w, req)
|
|
return
|
|
}
|
|
assetHandler.ServeHTTP(w, req)
|
|
}))
|
|
}
|
|
|
|
// isHTMLPath reports whether p resolves to an HTML document rather than a
|
|
// hashed build asset: the root (served as index.html by http.FileServerFS)
|
|
// or anything ending in .html.
|
|
func isHTMLPath(p string) bool {
|
|
return p == "/" || p == "" || strings.HasSuffix(p, ".html")
|
|
}
|
|
|
|
func (srv *Server) GetConfig(w http.ResponseWriter, req *http.Request) {
|
|
rest.WriteResponseJSON(w, http.StatusOK, dto.Config{
|
|
ServerURL: strings.TrimRight(srv.url, "/"),
|
|
})
|
|
}
|
|
|
|
func NewServer(db *database.DB, log zerolog.Logger, url string, allowIndexing bool, fileMaxAge int, fileImmutable bool) *Server {
|
|
srv := &Server{
|
|
url: url,
|
|
|
|
allowIndexing: allowIndexing,
|
|
|
|
db: db,
|
|
log: log.With().Str("namespace", "server.Server").Logger(),
|
|
|
|
fileCache: middlewares.NewCacheControl(fileMaxAge, fileImmutable),
|
|
}
|
|
|
|
return srv
|
|
}
|