oficina / estilo Public

busy: the URL's version is the files' digest; HEAD sends Content-Length (review)

Claude Fable 5.1 (approve): nothing tied the prefix's v1 to the bytes, so a
future re-copy could forget to bump it and browsers would keep the old
busy.js for a year at an immutable URL. The segment is now the digest.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 9d6a550c99c408afbf94f9366313e027612302a9 parent d0664ee
2 files changed, +18 −9
  • busy/busy.go +11 −3
  • busy/busy_test.go +7 −6

CI passed — run details

CI log
=== modcache ===
all modules verified
=== vet ===
CGO_ENABLED=0 go vet ./...
=== fmt ===
=== build ===
CGO_ENABLED=0 go build -buildvcs=false ./...
=== go-test ===
CGO_ENABLED=0 go test -count=1 ./...
ok  	amadan.net/oficina/estilo.git/base	0.012s
ok  	amadan.net/oficina/estilo.git/brand	0.010s
ok  	amadan.net/oficina/estilo.git/busy	0.010s
ok  	amadan.net/oficina/estilo.git/callui	0.019s
ok  	amadan.net/oficina/estilo.git/chrome	0.222s
?   	amadan.net/oficina/estilo.git/cmd/oficina-notices	[no test files]
ok  	amadan.net/oficina/estilo.git/credits	0.009s
ok  	amadan.net/oficina/estilo.git/credits/catalog	0.007s
ok  	amadan.net/oficina/estilo.git/homeconnect	0.008s
ok  	amadan.net/oficina/estilo.git/internal/lint	0.007s
?   	amadan.net/oficina/estilo.git/internal/tokencheck	[no test files]
ok  	amadan.net/oficina/estilo.git/menu	0.011s
ok  	amadan.net/oficina/estilo.git/savestatus	0.090s
ok  	amadan.net/oficina/estilo.git/sky	0.024s
ok  	amadan.net/oficina/estilo.git/theme	0.009s
diff --git a/busy/busy.go b/busy/busy.go
index c1410f8..0ec2fc4 100644
--- a/busy/busy.go
+++ b/busy/busy.go
@@ -21,8 +21,11 @@
package busy
import (
+ "crypto/sha256"
_ "embed"
+ "encoding/hex"
"net/http"
+ "strconv"
"strings"
)
@@ -44,9 +47,13 @@ func JS() []byte { return append([]byte(nil), busyJS...) }
// CSS is the spinner-in-place block.
func CSS() []byte { return append([]byte(nil), busyCSS...) }
-// Prefix is the URL path Handler serves under. The version moves whenever
-// either file's bytes do: both are cached for a year.
-const Prefix = "/static/estilo/busy/v1/"
+// Prefix is the URL path Handler serves under. Its version segment is the
+// two files' own digest, so new bytes are always a new URL: both are
+// cached for a year, immutably, and no one has to remember to bump it.
+var Prefix = func() string {
+ sum := sha256.Sum256(append(append([]byte(nil), busyJS...), busyCSS...))
+ return "/static/estilo/busy/" + hex.EncodeToString(sum[:6]) + "/"
+}()
// Handler serves GET for Prefix + "busy.js" and Prefix + "busy.css", and
// nothing else. Public and sessionless: the sign-in form needs it too.
@@ -72,6 +79,7 @@ func Handler() http.Handler {
w.Header().Set("Content-Type", kind)
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
w.Header().Set("X-Content-Type-Options", "nosniff")
+ w.Header().Set("Content-Length", strconv.Itoa(len(body)))
if r.Method == http.MethodHead {
return
}
diff --git a/busy/busy_test.go b/busy/busy_test.go
index 4e750ca..461afe2 100644
--- a/busy/busy_test.go
+++ b/busy/busy_test.go
@@ -55,12 +55,13 @@ func TestTheShimLeftTheRuleToBusyJS(t *testing.T) {
func TestHandlerServesOnlyItsTwoFiles(t *testing.T) {
h := Handler()
for path, want := range map[string]int{
- Prefix + "busy.js": 200,
- Prefix + "busy.css": 200,
- Prefix + "rastrillo.js": 404,
- Prefix + "../busy.go": 404,
- Prefix: 404,
- "/static/busy.js": 404,
+ Prefix + "busy.js": 200,
+ Prefix + "busy.css": 200,
+ Prefix + "rastrillo.js": 404,
+ Prefix + "../busy.go": 404,
+ Prefix: 404,
+ "/static/busy.js": 404,
+ "/static/estilo/busy/v1/busy.js": 404, // the version is the digest, not a counter
} {
rec := httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, path, nil))