fix: set Vary: Accept-Encoding for preCompressed responses#586
Conversation
When preCompressed is enabled the served file is chosen from the request Accept-Encoding header, but the response did not advertise that it varies on that header. A shared cache could then return a compressed variant to a client that does not accept it, or the uncompressed fallback to a client that does. Add Vary: Accept-Encoding to every preCompressed response, including the uncompressed fallback, and append to any Vary header already set through setHeaders without creating duplicates. Closes fastify#230
|
Heads up on the red CI: the only failing job is Test (24, macos-latest), and the single failure there is the pre-existing "with fastify-compress" test timing out after 30s because it reaches out to the network. All seven of the new Vary: Accept-Encoding tests pass on that same run, and every other OS and Node version is green. The timeout is unrelated to this change, so a re-run should clear it. |
Switch from reply.raw.getHeader to reply.getHeader to stay on the fastify reply API. Replace the split/some pattern with a plain for loop that walks comma-separated tokens without allocating an array. Behavior is identical: accept-encoding is appended only when not already present, and a wildcard Vary skips the append too.
|
Thanks for the feedback. Both points are addressed in the latest commit. For the first comment, I switched from reply.raw.getHeader to reply.getHeader so the Vary header is read through the fastify reply API rather than dropping to the raw response object. For the second comment, I replaced the split/some pattern with a plain for loop that walks the comma-separated tokens directly by tracking a start index. No array is allocated and no split call is made. The logic is otherwise the same: if accept-encoding or * is already present, we bail out early; otherwise we append accept-encoding at the end. All 327 tests still pass with 100% coverage. |
Signed-off-by: Manuel Spigolon <manuel.spigolon@nearform.com>
Signed-off-by: KaKa <23028015+climba03003@users.noreply.github.com>
What
When
preCompressedis enabled, the response variant is selected from the requestAccept-Encodingheader (brotli, then gzip, then the uncompressed original). The handler setcontent-encodingaccordingly but never setVary: Accept-Encoding, so the response did not declare that it depends on that request header.This is the issue reported in #230. Without
Vary: Accept-Encoding, a shared cache can store one variant and serve it to a client that negotiates a different one, for example handing a brotli body to a client that did not sendAccept-Encoding: br, or returning the uncompressed fallback to a client that does accept compression.Change
The
filebranch now addsVary: Accept-EncodingwheneverpreCompressedis enabled. It is added for the uncompressed fallback too, since that response is still the result of content negotiation and a cache must key on the encoding to stay correct.A small
addVaryAcceptEncodinghelper reads the currentvaryvalue from the raw response so a header already set throughsetHeadersis preserved, appendsaccept-encodingonly when it is not already present, and leaves an existingVary: *untouched. Nothing changes whenpreCompressedis disabled.Tests
Added cases under
test/static.test.js:Vary: Accept-Encodingwhen serving a compressed fileVary: Accept-Encodingwhen falling back to the uncompressed fileVaryheader set viasetHeaders, including an array value, without duplicatingaccept-encodingVary: *preCompressedis disablednpm run lintpasses and the new tests pass. The READMEpreCompressedsection documents the behavior.Closes #230