carlos / platform Public

edge: gzip eligibility predicate

Header-only, decided once. A missing Content-Type is refused rather
than sniffed: sniffing runs on bytes we would have already compressed
and would label the response application/x-gzip.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NoqzkUVahW5ykubsBQitxc
Paul Campbell 3a5a706d716d094d9a5898bf6f85610f3256bca4 parent d80b269
2 files changed, +173 −0
  • internal/edge/compress.go +86 −0
  • internal/edge/compress_test.go +87 −0
diff --git a/internal/edge/compress.go b/internal/edge/compress.go
index 78e2ca286..4d3de221a 100644
--- a/internal/edge/compress.go
+++ b/internal/edge/compress.go
@@ -61,3 +61,89 @@ func qValue(params string) float64 {
}
return 1
}
+
+// minCompressSize is the Content-Length below which compressing is not worth
+// the CPU: gzip's own header and trailer are 18 bytes, and a sub-kilobyte
+// response often lands in one packet either way. A response whose length is
+// UNKNOWN (streamed, chunked) is compressed regardless — refusing those would
+// exempt most proxied HTML, which is the bulk of what there is to save.
+const minCompressSize = 1024
+
+// compressible reports whether a response with these headers and this status
+// should be gzipped. The request-side questions — does the client accept
+// gzip, is this a HEAD — are settled once at wrapper construction
+// (newGzipWriter), so this reads the response alone.
+//
+// Every false below is a response that would be WRONG or pointless to
+// compress, not merely unprofitable:
+//
+// - 204/205 have no body; 304 has none and must keep the validators of the
+// representation it refers to; 206 is a byte range of a larger body, and
+// compressing a range makes the offsets it names meaningless.
+// - An existing Content-Encoding means the origin already encoded it.
+// Compressing again produces a body no client can decode without knowing
+// we did it.
+// - Cache-Control: no-transform is the origin forbidding exactly this
+// (RFC 9110 §5.5).
+//
+// A MISSING Content-Type is refused rather than sniffed. Sniffing is the
+// stdlib's job and it runs on the bytes we would have already compressed —
+// it would read gzip's magic number and label the response application/x-gzip.
+func compressible(h http.Header, status int) bool {
+ switch status {
+ case http.StatusNoContent, http.StatusResetContent,
+ http.StatusPartialContent, http.StatusNotModified:
+ return false
+ }
+ if h.Get("Content-Range") != "" {
+ return false
+ }
+ if h.Get("Content-Encoding") != "" {
+ return false
+ }
+ if hasCCDirective(joinHeader(h, "Cache-Control"), "no-transform") {
+ return false
+ }
+ if n, err := strconv.Atoi(h.Get("Content-Length")); err == nil && n < minCompressSize {
+ return false
+ }
+ return compressibleType(h.Get("Content-Type"))
+}
+
+// compressibleType reports whether a media type is text underneath. text/* is
+// the bulk of it; the structured suffixes (+json, +xml) catch types nobody has
+// enumerated yet, which is why they are here rather than a longer switch.
+//
+// text/event-stream is the one text type excluded: it is an open stream a
+// client consumes event by event, and buffering it inside a compressor is a
+// hang, not a saving.
+func compressibleType(ct string) bool {
+ mt, _, _ := strings.Cut(ct, ";")
+ mt = strings.ToLower(strings.TrimSpace(mt))
+ if mt == "" || mt == "text/event-stream" {
+ return false
+ }
+ if strings.HasPrefix(mt, "text/") ||
+ strings.HasSuffix(mt, "+json") || strings.HasSuffix(mt, "+xml") {
+ return true
+ }
+ switch mt {
+ case "application/json", "application/javascript", "application/x-javascript",
+ "application/xml", "application/wasm":
+ return true
+ }
+ return false
+}
+
+// weakenETag downgrades a strong validator to a weak one, in place. A gzipped
+// body is a different representation of the same resource, so an ETag that
+// claims byte-for-byte equality with the identity body is a lie — and the one
+// thing that lie breaks is a Range request served against the wrong bytes.
+// Conditional requests keep working: If-None-Match compares weakly for GET,
+// so a client returning W/"abc" still matches the origin's "abc" and gets its
+// 304.
+func weakenETag(h http.Header) {
+ if et := h.Get("ETag"); strings.HasPrefix(et, `"`) {
+ h.Set("ETag", `W/`+et)
+ }
+}
diff --git a/internal/edge/compress_test.go b/internal/edge/compress_test.go
index 6ddda82e6..e68f92a0c 100644
--- a/internal/edge/compress_test.go
+++ b/internal/edge/compress_test.go
@@ -71,3 +71,90 @@ func TestQValue(t *testing.T) {
}
}
}
+
+// TestCompressibleEligibility walks the response-side gate. Each false case
+// is a response that would be WRONG or pointless to compress, not merely
+// unprofitable — a 206 range body, an already-encoded body, an origin that
+// said no-transform.
+func TestCompressibleEligibility(t *testing.T) {
+ base := func() http.Header {
+ h := http.Header{}
+ h.Set("Content-Type", "text/css")
+ return h
+ }
+ for _, tc := range []struct {
+ name string
+ mutate func(http.Header)
+ status int
+ want bool
+ }{
+ {"plain css", func(http.Header) {}, 200, true},
+ {"html error page", func(h http.Header) { h.Set("Content-Type", "text/html; charset=utf-8") }, 404, true},
+ {"json api", func(h http.Header) { h.Set("Content-Type", "application/json") }, 200, true},
+ {"svg", func(h http.Header) { h.Set("Content-Type", "image/svg+xml") }, 200, true},
+ {"long body", func(h http.Header) { h.Set("Content-Length", "4096") }, 200, true},
+ {"unknown length", func(h http.Header) { h.Del("Content-Length") }, 200, true},
+
+ {"no content type", func(h http.Header) { h.Del("Content-Type") }, 200, false},
+ {"png", func(h http.Header) { h.Set("Content-Type", "image/png") }, 200, false},
+ {"event stream", func(h http.Header) { h.Set("Content-Type", "text/event-stream") }, 200, false},
+ {"already encoded", func(h http.Header) { h.Set("Content-Encoding", "gzip") }, 200, false},
+ {"no-transform", func(h http.Header) { h.Set("Cache-Control", "max-age=60, no-transform") }, 200, false},
+ {"short body", func(h http.Header) { h.Set("Content-Length", "512") }, 200, false},
+ {"exactly at threshold", func(h http.Header) { h.Set("Content-Length", "1024") }, 200, true},
+ {"partial content", func(h http.Header) { h.Set("Content-Range", "bytes 0-99/4096") }, 206, false},
+ {"no content", func(http.Header) {}, 204, false},
+ {"reset content", func(http.Header) {}, 205, false},
+ {"not modified", func(http.Header) {}, 304, false},
+ } {
+ h := base()
+ tc.mutate(h)
+ if got := compressible(h, tc.status); got != tc.want {
+ t.Errorf("%s: compressible(%v, %d) = %v, want %v", tc.name, h, tc.status, got, tc.want)
+ }
+ }
+}
+
+// TestCompressibleTypeSuffixes: a media type we have never heard of still
+// compresses when its structured suffix says it is text underneath.
+func TestCompressibleTypeSuffixes(t *testing.T) {
+ for _, ct := range []string{
+ "application/manifest+json", "application/atom+xml", "application/ld+json",
+ "application/vnd.api+json", "text/plain", "application/javascript",
+ "application/wasm", "application/xml", "text/css;charset=UTF-8",
+ } {
+ if !compressibleType(ct) {
+ t.Errorf("compressibleType(%q) = false, want true", ct)
+ }
+ }
+ for _, ct := range []string{
+ "", "image/png", "video/mp4", "application/octet-stream",
+ "application/zip", "font/woff2", "text/event-stream",
+ } {
+ if compressibleType(ct) {
+ t.Errorf("compressibleType(%q) = true, want false", ct)
+ }
+ }
+}
+
+// TestWeakenETag: a gzipped body is a different representation, so its
+// validator must not claim byte-equality with the identity one. Weakening
+// keeps conditional requests working — the stdlib's If-None-Match check
+// compares weakly for GET — while dropping the strong-equality claim a
+// Range request would rely on.
+func TestWeakenETag(t *testing.T) {
+ for _, tc := range []struct{ in, want string }{
+ {`"abc123"`, `W/"abc123"`},
+ {`W/"abc123"`, `W/"abc123"`}, // already weak: untouched
+ {"", ""}, // absent: nothing to weaken
+ } {
+ h := http.Header{}
+ if tc.in != "" {
+ h.Set("ETag", tc.in)
+ }
+ weakenETag(h)
+ if got := h.Get("ETag"); got != tc.want {
+ t.Errorf("weakenETag(%q) -> %q, want %q", tc.in, got, tc.want)
+ }
+ }
+}