Brotli
I finally got around to enabling Brotli compression on Golem. Reading the manual, I came across the BrotliAlterETag
directive:
Syntax:
BrotliAlterETag AddSuffix|NoChange|Remove
with the description:
- AddSuffix
- Append the compression method onto the end of the ETag, causing compressed and uncompressed representations to have unique ETags. In another dynamic compression module,
mod_deflate
, this has been the default since 2.4.0. This setting prevents serving “HTTP Not Modified (304)
” responses to conditional requests for compressed content.- NoChange
- Don’t change the ETag on a compressed response. In another dynamic compression module,
mod_deflate
, this has been the default prior to 2.4.0. This setting does not satisfy the HTTP/1.1 property that all representations of the same resource have unique ETags.- Remove
- Remove the ETag header from compressed responses. This prevents some conditional requests from being possible, but avoids the shortcomings of the preceding options.
Sure enough, it turns out that ETags+compression have been completely broken in Apache 2.4.x. Two methods for saving bandwidth, and delivering pages faster, cancel each other out and chew up more bandwidth than if one or the other were disabled.
To unpack this a little further, the first time your browser requests a page, Apache computes a hash of the page and sends that along as a header in the response
etag: "38f7-56d65f4a2fcc0"
When your browser requests the page again, it sends an
If-None-Match: "38f7-56d65f4a2fcc0"
header in the request. If that matches the hash of the page, Apaches sends a “HTTP Not Modified (304)
” response, telling your browser the page is unchanged from the last time it requested it.
If the page is compressed, using mod_deflate
, then the header Apache sends is slightly different
etag: "38f7-56d65f4a2fcc0-gzip"
So, when your browser sends its request with an
If-None-Match: "38f7-56d65f4a2fcc0-gzip"
header, Apache compares “38f7-56d65f4a2fcc0-gzip
” with the hash of the page, concludes that they don’t match, and sends the whole page again (thus wasting all the bandwidth you originally saved by sending the page compressed).
This is completely brain-dead. And, even though the problem has been around for years, the Apache folks don’t seem to have gotten around to fixing it. Instead, they just replicated the problem in mod_brotli
(with a “-br
” suffix replacing “-gzip
”).
The solution is drop-dead simple. Add the line
RequestHeader edit "If-None-Match" '^"((.*)-(gzip|br))"$' '"$1", "$2"'
to your Apache configuration file. This gives Apache two ETags to compare with: the one with the suffix and the original unmodified one. The latter will match the hash of the file and Apache will return a “HTTP Not Modified (304)
” as expected.
Why Apache didn’t just implement this in their code is beyond me.
Re: Brotli
hmm, but now you got the issue that if you send a request with
if-none-match: "0-5debc62fd30dd-br"
it’ll respond withetag: "0-5debc62fd30dd"
(which is missing the-br
part)something like this could solve it though
SetEnvIf If-None-Match '^"((.*)-(gzip))"$' gzip
SetEnvIf If-None-Match '^"((.*)-(br))"$' br
RequestHeader edit "If-None-Match" '^"((.*)-(gzip|br))"$' '"$1", "$2"'
Header edit "ETag" '^"(.*)"$' '"$1-gzip"' env=gzip
Header edit "ETag" '^"(.*)"$' '"$1-br"' env=br