84 lines
2.4 KiB
Go
84 lines
2.4 KiB
Go
package apps
|
||
|
||
import (
|
||
"crypto/sha256"
|
||
"encoding/hex"
|
||
_ "embed"
|
||
"sync"
|
||
)
|
||
|
||
// Embedded fallback: tool HTMLs from the time the binary was built.
|
||
// Used as a last-resort served-bytes when (cache miss) AND (upstream
|
||
// unreachable) AND (no operator override) — see handler.go.
|
||
//
|
||
// The files are populated by the top-level build.sh, which copies the
|
||
// freshly-built dist/<tool>.html into ./embedded/ before `go build` runs.
|
||
// Empty placeholder files are checked in so the package compiles when no
|
||
// build has been run yet (CI bootstrap, fresh clone, etc.); at runtime an
|
||
// empty embedded body is treated as "no embedded fallback available."
|
||
|
||
//go:embed embedded/archive.html
|
||
var embeddedArchive []byte
|
||
|
||
//go:embed embedded/transmittal.html
|
||
var embeddedTransmittal []byte
|
||
|
||
//go:embed embedded/classifier.html
|
||
var embeddedClassifier []byte
|
||
|
||
//go:embed embedded/index.html
|
||
var embeddedLanding []byte
|
||
|
||
//go:embed embedded/browse.html
|
||
var embeddedBrowse []byte
|
||
|
||
// EmbeddedBytes returns the embedded HTML for app, or nil if either app is
|
||
// not one of the canonical names or the embedded slot is empty (no build
|
||
// has populated it).
|
||
func EmbeddedBytes(app string) []byte {
|
||
var b []byte
|
||
switch app {
|
||
case "archive":
|
||
b = embeddedArchive
|
||
case "transmittal":
|
||
b = embeddedTransmittal
|
||
case "classifier":
|
||
b = embeddedClassifier
|
||
case "landing":
|
||
b = embeddedLanding
|
||
case "browse":
|
||
b = embeddedBrowse
|
||
default:
|
||
return nil
|
||
}
|
||
if len(b) == 0 {
|
||
return nil
|
||
}
|
||
return b
|
||
}
|
||
|
||
// EmbeddedETag returns a strong ETag (sha256-hex prefix, 32 chars) for the
|
||
// app's embedded bytes. Computed lazily on first call per-app and memoized
|
||
// — the embedded slot is fixed for the binary's lifetime, so the ETag
|
||
// changes only when the binary is redeployed. Empty slot returns "".
|
||
//
|
||
// Used by apps.Server.serveEmbedded to issue conditional-GET-friendly
|
||
// responses: with this ETag + Cache-Control: max-age=0, must-revalidate,
|
||
// every page load revalidates and gets a 304 unless the binary has been
|
||
// updated. Saves re-transmitting 50–920 KB tool HTMLs on every reload.
|
||
func EmbeddedETag(app string) string {
|
||
if v, ok := etagCacheByApp.Load(app); ok {
|
||
return v.(string)
|
||
}
|
||
body := EmbeddedBytes(app)
|
||
if body == nil {
|
||
return ""
|
||
}
|
||
sum := sha256.Sum256(body)
|
||
etag := hex.EncodeToString(sum[:])[:32]
|
||
etagCacheByApp.Store(app, etag)
|
||
return etag
|
||
}
|
||
|
||
// etagCacheByApp memoizes EmbeddedETag results keyed by app name.
|
||
var etagCacheByApp sync.Map
|