Open harigovindan opened 3 years ago
Notice a similar issue today. If I compare the original .br file in an hexadecimal editor with what I get from my server via cURL, I noticed the last byte from nginx’s response gets truncated:
# Both files index.html & index.html.br exist on the server.
curl -H 'Accept-Encoding: br' -Ls https://myserver.example.com/ > response.br
xxd index.html.br > index.html.br.hex
xxd response.br > response.br.hex
diff index.html.br.hex response.br.hex
# Diff output below
< 00000200: 6f3a 1a7c d29d 3c o:.|..<
---
> 00000200: 6f3a 1a7c d29d o:.|..
Then again, this issue was opened last year and no one ever responded. It doesn’t look like Google is supporting its own Brotli format very much anymore.
Although browsers do their best to decompress broken Brotli files, the brotli
command will simply give up:
brotli -dc index.html.br | diff index.html -; echo $?
# Outputs 0, meaning there was no difference between
# the uncompressed index.html.br and the original index.html.
brotli -dc response.br; echo $?
# Outputs 1 with error message: "corrupt input [response.br]"
Since this module doesn’t look like it’s being updated much anymore—the last commit was earlier this year, but the one before that was two years ago—despite the smaller file sizes, I’ll just ditch Brotli and stick with gzip for now.
Okay, so I chose to dig deeper into this issue and I still can’t find a solution.
First, I disabled brotli_static
and added directives in my config myself to do the same thing than brotli_static
does. Meaning, the server is requested to serve index.html
, if index.html.br
exists, it will serve that with the Content-Encoding: br
header:
location / {
try_files $uri $uri/ =404;
if ($http_accept_encoding ~* 'br(,.*)?') {
set $accept_br 1;
}
if (-e $request_filename.br) {
set $br_exists 1;
}
set $br "${accept_br}_${br_exists}";
if ($br = 1_1) {
rewrite ^(.*)$ $1.br last;
}
}
location ~ \.css\.br$ {
types {
text/css br;
}
add_header content-encoding br;
}
location ~ \.html\.br$ {
types {
text/html br;
}
add_header content-encoding br;
}
location ~ \.(js|mjs)\.br$ {
types {
application/javascript br;
}
add_header content-encoding br;
}
location ~ \.svg\.br$ {
types {
image/svg+xml br;
}
add_header content-encoding br;
}
To my amazement, it works like it should:
curl -H 'Accept-Encoding: br' -Ls -o /dev/null -D - https://myserver.example.com/ > test.br
HTTP/2 200
date: Sun, 27 Nov 2022 03:03:14 GMT
content-type: text/html
content-encoding: br
But what surprises me even more, is I’m having the exact same problem than before. Meaning, the last byte of the Brotli response is also truncated in this case. So, maybe it’s an issue with nginx? Then again, any other file, even gzip’d, works just fine. Even other Brotli’d files work okay.
I opened the problematic .br file in an hex editor and added a single null byte at the end (00).
And now, when comparing the uncompressed content of the .br file and the uncompressed Brotli response, there is no longer any difference. Yes, I’ve also tried with my custom rules removed and brotli_static
back on.
I can’t find any problems with the file sizes either:
File | Version | Size (Bytes) |
---|---|---|
index.html | Original | 1612 |
index.html.br | Original | 519 |
index.html.br | Edited | 520 |
So I guess the safe way now, besides disabling Brotli, is to add a null byte at the end of every Brotli file?
¯\_(ツ)_/¯
Finally, the deeper I go, the less of an answer I have for anyone. My apologies.
The problem I was having was only with one index.html.br
file from a site built using Middleman. It was compressed using the middleman-brotli
extension. That would output the 519-byte file that was somehow problematic with nginx, even without the brotli extension.
But then if I simply run brotli -q 11 -f index.html
, I get a whole different file, but is also 520 bytes in size. That, somehow, nginx has no problem serving.
So I just removed the middleman-brotli
extension, which is old. Then, I wrote my own after_build
hook to compress the files:
# Middleman site config.rb
after_build do |builder|
builder.thor.run "bin/build_brotli build"
end
#!/bin/sh
# bin/build_brotli
build_brotli_main() {
command -v brotli > /dev/null 2>&1 \
|| ( echo "Brotli not installed” >&2 && return 16 )
[ $# -lt 1 ] && echo "Specify directory with source files.” >&2 && return 17
find "$1" \
\( \
-iname '*.css' \
-or -iname '*.htm' \
-or -iname '*.html' \
-or -iname '*.js' \
-or -iname '*.otf' \
-or -iname '*.svg' \
-or -iname '*.ttf' \
-or -iname '*.woff' \
-or -iname '*.woff2' \
-or -iname '*.xhtml' \
\) \
-exec brotli -fq 11 {} \; \
;
}
build_brotli_main "$@"
I don’t know who’s still using Middleman out there, with Brotli on nginx, but maybe the above can be of use to you. That’s doing my job for now.
Sorry I can’t be much help for the issue at hand. Hopefully what I went through can provide clues to the next developer who may want to troubleshoot this.
When we try to load js/css files on browser with brotli levels set to either 2 or 3, we are seeing contents to be filtered. Full contents of the files are not rendered. The nginx version we are using is nginx/1.17.3. It turns out that the download is being truncated. We tested with all available compression levels and found that this issue is seen only on levels 2 and 3. However when we curl the file and redirect it to a file, the entire content is getting transferred. Issue is seen only on browser. The browser we are using is Mozilla Firefox 86.0 version.