clone

an offline map explorer where each world, from a game map to a city's open data, ships as one .atlas file

feat/atlas-next-horizon
atlas / internal / app / chrome.go
chrome.go 1.9K
1package app23import (4 "bytes"5 "net/http"6 "time"78 "github.com/FelineStateMachine/atlas/internal/app/assets"9)1011// The application's own chrome, served from the binary.12//13// /assets is a different mount from /static, and the difference is the14// deletability principle standing up in the URL space: /static is whatever15// tree a host handed over -- the seam's built bundle, which the application16// must work without -- and /assets is the part of the page that is the17// application's own. A build with no seam serves 404 under /static and a18// complete, styled, interactive page under /assets.1920// assetBodies is the whole mount. It is two entries because it is two things:21// the cascade and the runtime. Anything else a page needs belongs in one of22// them or in a template.23var assetBodies = map[string]struct {24 kind string25 body func() []byte26}{27 "app.css": {"text/css; charset=utf-8", assets.Stylesheet},28 "htmx.js": {"text/javascript; charset=utf-8", assets.Runtime},29}3031// assetEpoch is the modification time every asset reports. The bytes are32// compiled in, so they change exactly when the binary does and never while it33// is running; a fixed instant is the honest answer and gives conditional34// requests something stable to work with.35var assetEpoch = time.Unix(0, 0).UTC()3637// handleAsset serves one of the application's own assets.38func (a *App) handleAsset(w http.ResponseWriter, r *http.Request) {39 held, known := assetBodies[r.PathValue("path")]40 if !known {41 http.NotFound(w, r)42 return43 }44 w.Header().Set("Content-Type", held.kind)45 // The chrome is versioned by the binary rather than by a URL, so it is46 // revalidated rather than held forever: a development build swapped47 // under a running page must not serve yesterday's stylesheet.48 w.Header().Set("Cache-Control", "no-cache")49 http.ServeContent(w, r, r.PathValue("path"), assetEpoch, bytes.NewReader(held.body()))50}