Skip to content

[HttpCache] Add warning that Apache 2.4 mod_deflate changes ETag #12644

Closed
@mpdude

Description

@mpdude

In Apache 2.4, when mod_deflate compresses a response body, it will also modify the ETag header (if present) and append -gzip to it. This is to comply with HTTP specs and make sure that different representations of the resource also have different ETags.

Here's the relevant parts of results for two curl requests to a simple controller that just sets ETag: some-etag on the response. One is without compression, the other one enables it.

#> curl -I -X GET 'http://my.app/test'
HTTP/1.1 200 OK
Date: Sat, 16 Nov 2019 16:54:59 GMT
Server: Apache
ETag: "some-etag"    <--- HERE
Last-Modified: Fri, 15 Nov 2019 23:00:00 GMT
Vary: Accept-Encoding
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8

versus

#> curl -I -X GET 'http://my.app/test' --compressed
HTTP/1.1 200 OK
Date: Sat, 16 Nov 2019 16:56:25 GMT
Server: Apache
ETag: "some-etag-gzip"    <--- HERE
Last-Modified: Fri, 15 Nov 2019 23:00:00 GMT
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 10208
Content-Type: text/html; charset=UTF-8

From the client's perspective, "some-etag-gzip" is the opaque ETag value which will be included in revalidation requests.

Since Apache/mod_deflate fails to revert the change upon such requests (and probably cannot reasonably do so, since it cannot make assumptions about the meaning of the ETag), the modified value will be the one reaching PHP and Symfony. This effectively breaks ETag-based validation as outlined in the documentation. The controller code won't be short-cut and a 200 response will be generated instead of 304.

This behavior has been reported as a bug in Apache back in 2008. From that report, it seems mod_brotli faces the same challenge.

A configuration option DeflateAlterETag has been added in Apache 2.5 to either turn off ETag modification (possibly causing other issues?) or to remove ETags altogether from compressed responses. For mod_brotli, a BrotliAlterETag switch is available in Apache 2.4 already.

A possible workaround suggested is to add the following to the web server configuration:

RequestHeader edit "If-None-Match" '^"((.*)-(gzip|br))"$' '"$1", "$2"'

This will turn request headers like If-None-Match: "some-etag-gzip" into If-None-Match: "some-etag-gzip", "some-etag". I can confirm that this passes Symfony's \Symfony\Component\HttpFoundation\Response::isNotModified() as one would expect.

My suggestion is to add a warning/heads-up notice at https://symfony.com/doc/current/http_cache/validation.html#validation-with-the-etag-header. This could include the workaround configuration (if we want to endorse it) and/or point to this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions