Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

#61

Compress hosted site responses too

merged · by keenan 12d ago · merged into main at a76b2f38 by keenan 12d ago

keenan opened this 12d ago

Summary

Hosted sites were the one HTTP surface with no compression. Server.Handler dispatches a site host to server/pages before Web.Middleware runs, so a site — a static file server, the best case gzip has — was sending raw HTML, CSS and JavaScript, while the forge UI next door was compressed.

What changed

  • server/web/compress.go moved to internal/compress so both surfaces share one implementation of the negotiation and header rules instead of a copy. Two entry points, because the callers differ:
    • compress.New(rw, r) for the forge, which needs the writer inside its middleware: the status recorder under it feeds the page-view metric and the panic path, so the compressor has to sit above it rather than wrap the whole handler;
    • compress.Handler(next) for the site dispatcher, which is a plain wrap-serve-close.
  • server/server.go: the pages dispatcher is wrapped once, at the point both surfaces pass through.
  • docs/operations.md: the compression baseline now says hosted sites are covered too.

Nothing about the rules changed: gzip only when asked with q > 0, text-like types only, no Range, no already-encoded body, no tiny body, no 1xx/204/206/304/301, and a handler can still opt out with Cache-Control: no-transform.

Why this is safe for a site

  • A site’s ETag (the blob sha) and Last-Modified survive, so revalidation is still a 304 off the blob sha; Vary: Accept-Encoding names the representation.
  • A 304 and a directory’s 301 redirect carry no body to compress.
  • A client that does not ask for gzip gets the exact bytes and the exact Content-Length — the new test asserts the length equals the body for the identity case.
  • When a compressed body is small enough that net/http buffers it, the Content-Length it adds is the gzipped length, never the file’s. The test says so explicitly, because that distinction is exactly what a reader would get wrong.

Measurements

A hosted index.html of ~1.5 KB of repeated markup: 83 bytes gzipped, over the site host, with the ETag unchanged.

Verification

  • gofmt, go vet ./..., go test ./... — clean.
  • ./vole workflow run verify — ok: 1 job(s).
  • New: TestSiteResponsesAreCompressed (compressed page, identity client with an exact Content-Length, 304 with no encoding), plus the unit suite that moved with the package (22 negotiation cases, flush, unwrap).
  • Existing site tests are untouched and still pass; the pages package unit test that asserts Content-Length runs below the dispatcher, so it still checks the file server rather than the compressor.

Base

This sits on contrib/keenan/prism-and-compression (#60), which is where the compressor exists at all. Retarget when the lower requests land:

./vole change update <id> --base main

Unlike #60, the head branch is a plain name (site-compression): contrib/* is the contributor namespace, and this account is a repo admin, so it only put the tip behind the contributor CI gate for nothing. That is also why this request’s branches are named the way they are.

Not in this change

  • Brotli. Measured at +3.1 MB of binary for the Go encoder (the static dictionary alone is ~250 KB), to save ~40 KB per reader against gzip. The only version worth having is precompressing the static assets at vendor time, which is a separate decision.

Discussion (0)

No discussion yet.