Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ERR_CONTENT_DECODING_FAILED for wasm.wasm when Chrome doesn't accept 'br' compression #28

Closed
heisencoder opened this issue Nov 21, 2022 · 11 comments

Comments

@heisencoder
Copy link

I'm having trouble getting this server to properly run when connecting via a separate computer.

In particular, if I run this command (with my computer's IP address on the local network):

WASM_SERVER_RUNNER_ADDRESS=192.168.68.107 cargo run --release
or
WASM_SERVER_RUNNER_ADDRESS=0.0.0.0 cargo run --release

and then try to connect at http://192.168.68.107:1334/, the wasm.wasm file fails to load.

In looking at my network traffic (Chrome 107.0.5304.68 running on Ubuntu Linux 22.04), I see that these loads work correctly with status code 200:

However, when loading http://192.168.68.107:1334/api/wasm.wasm, I get no response.

Note that when I change WASM_SERVER_RUNNER_ADDRESS to something like 0.0.0.0 or 127.0.0.1, then visiting 127.0.0.1 does allow wasm.wasm to correctly load. However, attempting to load via the server's IP address fails.

(This is needed for me to load from a different computer on the LAN, since the other computer can't use 127.0.0.1)

@heisencoder
Copy link
Author

Here's a picture of the Network traffic tab in Chrome for reference:

screenshot_20221120_193135

The Status was (failed)net::ERR_CONTENT_DECODING_FAILED and the JS error was Uncaught (in promise) TypeError: Failed to fetch

Here are the lines around wasm.js with the failure:

                async function init(input) {
                    
                    const imports = getImports();

                    if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
                        input = fetch(input);   # <---- failure on this call, line 1997
                    }
...

@heisencoder
Copy link
Author

In comparing the headers of a successful request to the failing, I see these difference:

Successful request (to 127.0.0.1):

GET /api/wasm.wasm HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Host: 127.0.0.1:1334
Referer: http://127.0.0.1:1334/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36
sec-ch-ua: "Google Chrome";v="107", "Chromium";v="107", "Not=A?Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Linux"

Failed attempt (to 192.168.68.107):

Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Host: 192.168.68.107:1334
Referer: http://192.168.68.107:1334/
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36

I noticed that with the failed request, the Accept-Encoding is missing br. Perhaps the server is returning br encoding without checking the Accept-Encoding header?

@heisencoder
Copy link
Author

And I see that the server is basically only able to serve br:

([("content-encoding", "br")], WithContentType("application/wasm", compressed_wasm))
.

So in summary, I think for some reason Chrome is not accepting br when using the server's IP address, but the server is only able to deliver br compressed files. Maybe a solution would be to also support gzip?

@daxpedda
Copy link
Contributor

Basically revert #22 or consider adding more compression formats to the WASM file.
Isn't this a bug in Chrome btw?

@heisencoder
Copy link
Author

Thanks @daxpedda ! That looks like a fairly straight-forward solution! I'm thinking of a couple possible approaches for the fix then:

  1. Parse the Accept-Encoding field and preferentially return br, but fall back to gzip or uncompressed.
  2. Just go back to gzip (Not ideal due to the lower compression ratio of gzip)
  3. Make this configurable via environment variables

With regard to being a Chrome bug, I don't have insight into this, but can speculate that it may have something to do with loading from JavaScript or maybe some heuristic that files on local networks shouldn't be compressed if the transfer rate is high enough (I'm guessing that for localhost, compression is probably just a waste of time, and for a gigabit/s LAN network, maybe only light compression is needed, if any).

The compression timing table in #22 (comment) shows that gzip outperforms br compression at the lowest compression levels (e.g., level 1-2), and probably performs better on fast networks, so Chrome might take this into account.

I started working on a PR last night, and was attempting to start with testing-first. To avoid needing to test a full-blown server, I did some light refactoring to server::run_server to pull out the logic that creates the Router and then added some testing just for that portion of the server (since that's the logic that is failing). Let me know if such a refactoring and light testing would be accepted in addition to functional changes to support gzip.

Thanks!

@heisencoder heisencoder changed the title wasm.wasm not served when setting WASM_SERVER_RUNNER_ADDRESS to the server's IP ERR_CONTENT_DECODING_FAILED for wasm.wasm when Chrome doesn't accept 'br' compression Nov 23, 2022
heisencoder added a commit to heisencoder/wasm-server-runner that referenced this issue Nov 23, 2022
See jakobhellermann#28 for context.

This new code will serve up br compressed if supported, but will
fall back to gzip compression if not.
heisencoder added a commit to heisencoder/wasm-server-runner that referenced this issue Nov 24, 2022
See jakobhellermann#28 for context.

This new code will serve up br compressed if supported, but will
fall back to gzip compression if not.
@chessai
Copy link

chessai commented Feb 1, 2023

Just ran into this. Thanks @heisencoder for the debugging and the subsequent PR!

@daxpedda
Copy link
Contributor

Just hit the same problem and remembered this, did some research on it and found this:
https://hacks.mozilla.org/2015/11/better-than-gzip-compression-with-brotli/

The reason to limit brotli to secure contexts is that intermediaries (specifically, buggy proxies and content scanners) tend to behave very poorly when they encounter non-deflate/gzip Content-Encoding. The Google guys discovered this when they rolled out ‘sdch’ and ‘bzip2’ before that; they ended up pulling bzip2 partly for that reason and sdch has a number of hacks that they had to put in. By requiring HTTPS for brotli, they can head off this problem in most cases because comparatively few content-scanners MITM HTTPS streams.

So that's the reason why both Chrome and Firefox don't support Brotli with HTTP except on localhost, because localhost counts as a secure context.

@jakobhellermann
Copy link
Owner

I've switched to just using tower_http compression in 5eeb8d5. This should mean that this error is fixed now, right?

@heisencoder
Copy link
Author

Unfortunately, I don't have my Rust environment from last year available right now to verify, so am hoping someone else could verify.

@ndev-rs
Copy link

ndev-rs commented Nov 21, 2023

Unfortunately, I don't have my Rust environment from last year available right now to verify, so am hoping someone else could verify.

I can confirm this works for me on Firefox and Chromium. Thanks @jakobhellermann, @heisencoder, et al!

@jakobhellermann
Copy link
Owner

nice. Also sorry again for taking a year to fix this 😅

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

No branches or pull requests

5 participants