Precompressed Brotli File in Workers Static Asset is not decompressed properly

Previously I have been putting .js.br file in Cloudflare Pages (React+Vite, linked to git and run build script in Pages) I'm migrating to Workers, but when I put the same file in public folder, even with _headers (same as when using Pages) set to:
/WebGL/Build/*.js.br
Content-Encoding: br
Content-Type: application/javascript
/WebGL/Build/*.js.br
Content-Encoding: br
Content-Type: application/javascript
when the file is fetched, I get jumbled Response instead of the normally decompressed js file. The Response Headers have the content-encoding : br and content-type : application/javascript wrangler.jsonc:
"assets": {
"not_found_handling": "single-page-application",
"directory": "./dist/client",
"binding": "ASSETS",
"run_worker_first": [ "/api/*" ]
},
"assets": {
"not_found_handling": "single-page-application",
"directory": "./dist/client",
"binding": "ASSETS",
"run_worker_first": [ "/api/*" ]
},
In the local environment the file works. Not sure what I'm missing here. Thanks in advance! Edit: It seems the one in Pages has "Content-Length" header, while in workers there isn't any. Is there a way to ensure there is one?
No description
6 Replies
Walshy
Walshy2mo ago
I'm a little confused, you're requesting a Brotli file and getting Brotli back with the appropriate content-encoding header. That sounds correct?
Ampere
AmpereOP2mo ago
Yes, but I'm using Unity so it seems that Content-Length header is also required. When I was using Pages it was there. Usually the response in Network tab is already decompressed. Then I'd get
Unable to parse /WebGL/Build/build.framework.js.br! This can happen if build compression was enabled but web server hosting the content was misconfigured to not serve the file with HTTP Response Header "Content-Encoding: br" present. Check browser Console and Devtools Network tab to debug.
Unable to parse /WebGL/Build/build.framework.js.br! This can happen if build compression was enabled but web server hosting the content was misconfigured to not serve the file with HTTP Response Header "Content-Encoding: br" present. Check browser Console and Devtools Network tab to debug.
Walshy
Walshy2mo ago
Can you share the url to test? Content-Length is generally not there for compressed content (which is fully spec compliant) That error seems to just want CE br which you have Chrome should be decompressing it though so wonder if it's actually bad data...
Ampere
AmpereOP2mo ago
url sent in DM For now I can put the game files in R2 and use them like this:
//worker/index.ts
export interface Env {
R2: R2Bucket;
}

export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
if (url.pathname.startsWith("/api/client/Build/")) {
const key = url.pathname.replace("/api/client/", "");
const object = await env.R2.get(key);

if (!object) return new Response("Not found", { status: 404 });

const headers: Record<string, string> = {
"Cache-Control": "public, max-age=31536000, immutable",
"Content-Length": object.size.toString()
};

if (key.endsWith(".js")) {
headers["Content-Type"] = "application/javascript";
} else if (key.endsWith(".js.br")) {
headers["Content-Type"] = "application/javascript";
headers["Content-Encoding"] = "br";
} else if (key.endsWith(".wasm.br")) {
headers["Content-Type"] = "application/wasm";
headers["Content-Encoding"] = "br";
} else if (key.endsWith(".data.br")) {
headers["Content-Type"] = "application/octet-stream";
headers["Content-Encoding"] = "br";
}

return new Response(object.body, { headers, encodeBody: "manual" });
}

return new Response("Not found", { status: 404 });
}
} satisfies ExportedHandler<Env>;
//worker/index.ts
export interface Env {
R2: R2Bucket;
}

export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
if (url.pathname.startsWith("/api/client/Build/")) {
const key = url.pathname.replace("/api/client/", "");
const object = await env.R2.get(key);

if (!object) return new Response("Not found", { status: 404 });

const headers: Record<string, string> = {
"Cache-Control": "public, max-age=31536000, immutable",
"Content-Length": object.size.toString()
};

if (key.endsWith(".js")) {
headers["Content-Type"] = "application/javascript";
} else if (key.endsWith(".js.br")) {
headers["Content-Type"] = "application/javascript";
headers["Content-Encoding"] = "br";
} else if (key.endsWith(".wasm.br")) {
headers["Content-Type"] = "application/wasm";
headers["Content-Encoding"] = "br";
} else if (key.endsWith(".data.br")) {
headers["Content-Type"] = "application/octet-stream";
headers["Content-Encoding"] = "br";
}

return new Response(object.body, { headers, encodeBody: "manual" });
}

return new Response("Not found", { status: 404 });
}
} satisfies ExportedHandler<Env>;
But is it a waste of workers resource to transform R2 like this every time? Correct me if I'm wrong but should static assets in worker itself be better?
Walshy
Walshy2mo ago
Ok sorry just getting back to this This is weird.. Ok I see... it's double-compressed
$ file unity-js.br
unity-js.br: data
$ brotli -d unity-js.br
$ mv unity-js unity-js-2.br
$ brotli -d unity-js-2.br
$ file unity-js-2
unity-js-2: ASCII text, with very long lines (65127)
$ file unity-js.br
unity-js.br: data
$ brotli -d unity-js.br
$ mv unity-js unity-js-2.br
$ brotli -d unity-js-2.br
$ file unity-js-2
unity-js-2: ASCII text, with very long lines (65127)
Guessing the Pages path makes it work due to o2o But for Workers, we see the eyeball wants brotli and compress it :blobcatsweats: You could disable brotli compression with a compression rule but I'm not sure that "solves" it, we may then omit the content-encoding header
Ampere
AmpereOP2mo ago
I've tried it with both compression rule and header change. I doesn't seem to work. I seems I need to put it on R2 and use worker.

Did you find this page helpful?