Skip to content

feat(compress): Decode brotli and zstd response bodies - #90

Merged
korya merged 1 commit into
masterfrom
korya-feat-brotli-zstd
Aug 11, 2026
Merged

feat(compress): Decode brotli and zstd response bodies#90
korya merged 1 commit into
masterfrom
korya-feat-brotli-zstd

Conversation

@korya

@korya korya commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Problem

A CDN response cannot have its payload asserted on, because brotli and zstd bodies are never decoded.

decoders knew only gzip and deflate, so --assert-body, --assert-body-eq and --assert-jq refused by name while --assert-ok, --assert-status and --assert-header* carried on:

$ http-assert --assert-body '"status":"success"' https://cdn.example.com/
- body: response is br-encoded and was not decoded: no decoder for "br"; gzip and deflate are supported

Brotli is the default for most CDNs and zstd is a registered coding (RFC 8878) being switched on across them. Testing content negotiation with -H 'Accept-Encoding: br' is an ordinary thing to do, and it was the one thing this tool could not follow through on.

Solution

Add both decoders — andybalholm/brotli and klauspost/compress/zstd — and derive the "supported" list from the decoder map so it can no longer drift.

The dependency question, with numbers instead of adjectives

The count was cut to three deliberately when viper went (#54), so both tickets asked for this to be weighed rather than assumed. Nothing tracked binary size, so it was measured at release flags (-trimpath -ldflags="-s -w", CGO_ENABLED=0):

Size Δ
Before 7.38 MB
+ brotli 7.62 MB +233 KB
+ brotli + zstd 7.80 MB +419 KB (+5.7%)

Both modules bring zero transitive dependencies. #78 worried that klauspost/compress is "considerably larger than a brotli-only package" — true of the repository, not of the binary: only the zstd package links, so it costs 186 KB, less than brotli.

The build-tag alternative was rejected because it fits this project's distribution badly. Released binaries are how the tool is used, so a tag either ships releases that cannot decode the thing the change is about, or doubles the artifacts to ten and makes the user pick.

The part the tickets called "one map entry"

The suite used br throughout as its stand-in for an encoding with no decoder here — which stopped being true of it. That role moves to compress (LZW): RFC 9110 still registers it, effectively nothing serves it, and unlike br and zstd it is not a plausible candidate for support later, so the fixtures should stay honest this time.

The e2e brotli fixture was also fake by construction — "the bytes are not real brotli because nothing in the suite could produce them, and the CLI never gets far enough to care." With a decoder the CLI does get far enough: fed to a real decoder those bytes give unexpected EOF. /brotli now serves genuine brotli, /zstd genuine zstd, and /brotli-corrupt covers a failure that was unreachable before — a body claiming br that is not br.

Verification

New e2e tests cover decoding, --assert-jq over a decoded body, and that the Content-Encoding header still reports what arrived. They were mutation-checked: with the two map entries removed they fail, so they are testing the decoders rather than the fixtures.

Manually, both codings were decoded from streams produced by the reference implementationsbrotli 1.2.0 and zstd 1.5.7 — not only by Go's own writers, which is the interop the test suite cannot prove on its own. Brotli additionally end-to-end against jsdelivr and the npm registry, asserting the payload and the Content-Encoding header in the same run.

Other Changes

  • The failure now reads no decoder for "compress"; br, deflate, gzip, zstd are supported, built from the map, with an e2e test pinning it.
  • Package docs, --help and README updated; the README paragraph promising br and zstd "are not supported yet" is gone.
  • No visual change — CLI only.

Closes #77
Closes #78

Related:

🤖 Generated with Claude Code

A body arriving as br or zstd could not be asserted on at all: the three body
assertions refused by name while the status and header assertions carried on.
Both are what CDNs actually serve, so testing the payload behind content
negotiation -- the ordinary case -- was the one thing the tool could not do.

Neither coding is in the standard library, so each costs a dependency, and the
count was cut to three deliberately when viper went (#54). Measured at release
flags rather than guessed: brotli adds 233KB and zstd 186KB to a 7.38MB binary,
5.7% together, and both modules bring nothing transitively. klauspost/compress
is a large repository but only its zstd package links, so the cost is the
decoder rather than the library. That is a smaller price than a CDN-facing HTTP
assertion tool that cannot read CDN responses.

A build tag was the alternative and fits this project badly: released binaries
are how the tool is used, so a tag either ships releases that cannot decode the
thing the change is about, or doubles the artifacts and makes the user choose.

The list of supported codings in the failure is now derived from the decoder
map instead of being spelled out, so the next coding cannot be added while the
message still claims otherwise.

The test suite used br throughout as its stand-in for "an encoding with no
decoder here", which is no longer true of it. That role moves to compress
(LZW): RFC 9110 still registers it, effectively nothing serves it, and unlike
br and zstd it is not a plausible candidate for support later. Brotli also
gains a corrupt-stream fixture, a failure only reachable now that the bytes get
as far as a decoder.

Verified against streams from the reference implementations -- brotli 1.2.0 and
zstd 1.5.7 -- rather than only against Go's own writers, and brotli end to end
against jsdelivr and the npm registry.

Closes #77
Closes #78

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CrknafJSP5hF8u865cbnqX
@korya
korya force-pushed the korya-feat-brotli-zstd branch from d9da582 to e50374e Compare August 11, 2026 11:10
@korya
korya marked this pull request as ready for review August 11, 2026 11:22
@korya
korya merged commit 9b69b33 into master Aug 11, 2026
8 checks passed
@korya
korya deleted the korya-feat-brotli-zstd branch August 11, 2026 11:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: decode zstd (Content-Encoding: zstd) response bodies Feature: decode brotli (Content-Encoding: br) response bodies

1 participant