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

File gets truncated on browser for brotli level 2 and 3 #118

Open
harigovindan opened this issue Apr 12, 2021 · 4 comments
Open

File gets truncated on browser for brotli level 2 and 3 #118

harigovindan opened this issue Apr 12, 2021 · 4 comments

Comments

@harigovindan
Copy link

harigovindan commented Apr 12, 2021

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.

@remino
Copy link

remino commented Nov 27, 2022

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.

@remino
Copy link

remino commented Nov 27, 2022

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?

¯\_(ツ)_/¯

@remino
Copy link

remino commented Nov 27, 2022

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.

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

3 participants
@remino @harigovindan and others