From 21cb386e3d65427191e45692bde5d58f6e2d4648 Mon Sep 17 00:00:00 2001 From: officer Date: Wed, 19 Aug 2026 09:16:54 +0000 Subject: [PATCH] stop asking for compression, so nothing has to unpick it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every asset in the frame answered 502. `fetch()` decompresses transparently while `upstream.headers` still claims content-encoding: gzip, so re-emitting those headers with the decompressed body hands the next hop a lie and it dies with ZlibError. The HTML escaped it only because the sidecar deletes that header when it rewrites — which is why the page rendered and every stylesheet, script and image beside it failed. Not asking for compression removes the mismatch rather than chasing it through two hops. The cost is nil: this hop is loopback, and the browser's own connection is compressed by whatever terminates TLS. --- api/router.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/api/router.ts b/api/router.ts index ea9bfe8..14ca069 100644 --- a/api/router.ts +++ b/api/router.ts @@ -79,6 +79,18 @@ router_.all('/_web/*', async (ctx) => { for (const h of ['host', 'connection', 'keep-alive', 'transfer-encoding', 'upgrade', 'authorization']) { headers.delete(h); } + // ACCEPT-ENCODING GOES TOO, and this one is not tidiness. + // + // `fetch()` transparently decompresses a response, but `upstream.headers` still says + // `content-encoding: gzip`. Re-emitting those headers with the decompressed body hands the browser a + // lie, and it fails with ZlibError — reported here as a 502, which is what every asset in the frame + // was doing while the HTML rendered fine. The HTML escaped only because the sidecar drops that header + // when it rewrites. + // + // Asking for no compression at all removes the mismatch instead of chasing it through two hops. The + // cost is nil: this hop is loopback, and the browser's own connection to the platform is compressed by + // whatever terminates TLS. + headers.delete('accept-encoding'); // The sidecar binds loopback and trusts these, which is exactly why nothing but this may set them. headers.set('X-Officer-User', String(userId)); // Where the browser thinks the frame lives. The sidecar rewrites Gitea's HTML, redirects and cookies