mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-07 17:34:23 +00:00
fix(http): strip zstd from Accept-Encoding so compression sets Content-Encoding (#1092)
compression@1.8.1's negotiator silently fails to set the Content-Encoding response header when Accept-Encoding carries an unknown token like zstd, even though the body is still compressed with brotli. Chromium 123+ sends "Accept-Encoding: zstd, gzip, br" by default, including Playwright's bundled Chromium over plain HTTP, so any homelab Sencho viewed without a TLS terminator triggered ERR_CONTENT_DECODING_FAILED in the browser and rendered as a blank page. Insert a tiny middleware (step 4 of the canonical pipeline) that drops zstd tokens from Accept-Encoding before compression's negotiator runs. The negotiator then picks br or gzip and writes the matching header. The fix is no-op when zstd is absent. If only zstd was offered, the header falls back to identity so the response is served uncompressed. Renumber the canonical-order JSDoc in app.ts from 17 to 18 steps and update the step-number references in hub-only-guard and proxy-mount-order test docstrings to match. 11 new tests (8 unit + 3 supertest integration against real compression with a 3000-byte body) lock in the behavior: zstd-stripping across casing and q-values, surviving tokens preserved verbatim, identity fallback when only zstd was offered, Content-Encoding header always written when at least one supported codec remains.
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import type { RequestHandler } from 'express';
|
||||
|
||||
// compression@1.8.1's encoding negotiation (via negotiator@1.0.0) silently
|
||||
// breaks when Accept-Encoding carries an unknown token such as `zstd`, which
|
||||
// Chromium 123+ sends by default and which Playwright's bundled Chromium sends
|
||||
// even over plain HTTP. In that path the response body is still compressed
|
||||
// (brotli) but the Content-Encoding response header is never set, so the
|
||||
// browser decode-fails with ERR_CONTENT_DECODING_FAILED and the Sencho UI
|
||||
// renders blank. Drop the offending token before compression sees it so the
|
||||
// negotiator falls back to a supported encoding and the header is written.
|
||||
export const normalizeAcceptEncoding: RequestHandler = (req, _res, next) => {
|
||||
const raw = req.headers['accept-encoding'];
|
||||
if (typeof raw !== 'string' || !/\bzstd\b/i.test(raw)) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
const filtered = raw
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s.length > 0 && !/^zstd(\s*;\s*q\s*=.*)?$/i.test(s))
|
||||
.join(', ');
|
||||
req.headers['accept-encoding'] = filtered || 'identity';
|
||||
next();
|
||||
};
|
||||
Reference in New Issue
Block a user