27

I have added the following line in my Apache httpd.conf: -

AddOutputFilterByType DEFLATE text/html text/css application/javascript application/x-javascript application/json

I have a html file (test.html) with a script inclusion: -

<script type="text/javascript" src="/test.js"></script>

The problem is, every time I load test.html, test.js is also loaded with HTTP status: 200.

The question is: Why conditional GET is not satisfied?

If I comment out the "AddOutputFilterByType" line in httpd.conf, Apache sends 304.

If I enable AddOutputFilterByType in httpd.conf, the request header is: -

Host: optimize
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.0.10) Gecko/2009042316 Firefox/3.0.10 GTB5 (.NET CLR 3.5.30729) FirePHP/0.2.4
Accept: */*
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://optimize/
Cookie: PHPSESSID=nbq6h0eeahkshkcbc6ctu2j2b4
If-Modified-Since: Tue, 19 May 2009 07:06:46 GMT
If-None-Match: "2000000000717f-2c25a-46a3e8dcc2ad8"-gzip
Cache-Control: max-age=0

And the response header is: -

Date: Fri, 22 May 2009 07:03:40 GMT
Server: Apache/2.2.9 (Win32) PHP/5.2.6
Last-Modified: Tue, 19 May 2009 07:06:46 GMT
Etag: "2000000000717f-2c25a-46a3e8dcc2ad8"-gzip
Accept-Ranges: bytes
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 52583
Keep-Alive: timeout=5, max=98
Connection: Keep-Alive
Content-Type: application/javascript

UPDATE: I have noticed, if I am disabling ETag, it works properly. I mean it sends 304.

FileETag None

But I really want to keep ETag as it is (I know that there is a inode disclosure issue).

Sabya
  • 11,534
  • 17
  • 67
  • 94
  • 1
    Did you ever find a workaround? Apache 2.4 appears to be doing the same, always sending 200 response for gzip'd content – virtualeyes Jan 14 '13 at 17:40
  • Any reason you want to keep ETags? There's no real point in having them if you're just serving static content that already has a `Last-Modified` date for revalidation. Certainly, axing them would be the easiest RFC-compliant workaround for now. – a cat Oct 02 '16 at 21:09
  • 1
    ETags *are* needed. If you ever want to replace content with an older version (eg javascript file which needed to be reverted to an older version without a bug that was introduced) and when reverting the file's older date is used, then a simple date comparison won't suffice. – Colin 't Hart Jan 23 '17 at 08:55
  • Fascinating that this issue is still valid almost a decade after initial post - stumbling upon it in apache 2.4.35 in 2019. on latest Debian Stable at the moment. – Matija Nalis Jan 30 '19 at 03:26
  • @acat How can we keep Last-Modified in sync if we have multiple servers? – ankur_rajput Mar 19 '21 at 06:56
  • @Sabya For me, it is always returning 200. I am generating ETag from my backend server, so I have kept "FileETag None" and disabled mod_deflate as well. The ETags are also the same in the request and response headers but still, it is returning 200. – ankur_rajput Mar 19 '21 at 07:06

4 Answers4

30

This is a known bug in Apache. See Apache bug #45023, and summary of Apache 304 etags and mod_deflate.

Rebuilding from svn will fix the issue. The resolution was to revert the change that appended "-gzip" to the etag. However, there are associated HTTP compliance problems.

If you can't rebuild Apache, there is a suggested runtime configuration workaround in the bug report:

 RequestHeader  edit "If-None-Match" "^\"(.*)-gzip\"$" "\"$1\""
 Header  edit "ETag" "^\"(.*[^g][^z][^i][^p])\"$" "\"$1-gzip\""
CzBiX
  • 72
  • 6
p00ya
  • 3,659
  • 19
  • 17
  • 3
    On Apache 2.4, nothing but "200 OK" responses -- does this solution actually work? – virtualeyes Jan 14 '13 at 17:38
  • 4
    This is STILL an issue and I couldn't get above working either (in Apache 2.4.7). However, after some more digging, I've also decided that ETags aren't that useful in Apache anyway. An ETag would be most useful if it was a hash of the contents, so even if the timestamp changed, it can still be used to decide if the contents haven't. For Apache the ETag is a combination of inode, size and last modified data (with size and last Modified values used by default). So, since it uses file attributes, rather than a hash of the contents, I've decided to turn it off and use Last-Modified instead. – Barry Pollard Jun 06 '15 at 13:33
  • 2
    This seems to be STILL an issue? I'm using Apache 2.4.10 – Inna Dec 12 '16 at 21:28
  • @Inna Did you ever discover whether this is still an issue on apache 2.4? – thatidiotguy Jan 11 '17 at 14:54
  • @thatidiotguy not sure, if I have the same problem. Apache does not send 304 headers for .js and .css documents, images have 304 correctly. I'm proxying to nodejs. nodejs without apache sends all 304 correctly. – Inna Jan 12 '17 at 11:16
  • @Inna I actually confirmed yesterday that this bug still affects 2.4, and the above solution worked for me. – thatidiotguy Jan 12 '17 at 14:50
  • @thatidiotguy do you mean the solution with modifying the requestheader? This does not work for me. Or did you turned off ETags? I think ETags are a quite useful concept, it's not really a solution to turn them off... – Inna Jan 16 '17 at 12:29
  • RequestHeader edit "If-None-Match" '^"((.*)-gzip)"$' '"$1", "$2"' does work for me. from https://bz.apache.org/bugzilla/show_bug.cgi?id=45023 really strange that this bug is still an issue in apache 2.4.10 ... – Inna Jan 16 '17 at 12:46
  • worked for me using the docker image php:5.6-apache in order to have control on the cache on an angular app – Frank Aug 18 '20 at 23:37
11

"I've also decided that ETags aren't that useful in Apache anyway."

Wrong,
for example you have a file with modification date set to '2016.07.27 05:00:00', you upload it to your site, browser gets this file with HTTP code 200, then caches it and revalidates every time with HTTP 304.
Next you upload a file with the same filename again, but with older timestamp '2013.07.27 05:00:00' and with other content.

If ETag is disabled on server, browser will use only If-Modified-Since: request to determine if file was changed on server, so the request will be If-Modified-Since: 2016.07.27 05:00:00, but the file is not modified after this date, so a HTTP 304 is returned, even if the file has changed.

If ETag is enabled on server, besides If-Modified-Since:, there will be a If-None-Match: header coming from browser that will detect that file was changed(by default - timestamp mismatch+size mismatch) and the file will be redownloaded.


This problem still exists in Apache 2.4.23, so, I've written a better code than above to fix this issue. Expanation line by line:

    1) If the browser sends a 'If-None-Match' request which has '-gzip' at the end, set variable request_etag=gzip.
    2) Edit request header to strip out '-gzip' part.
    3) Edit response header to add '-gzip' part, but only if the browser sent a '-gzip' request initially or response content is gzip encoded.


You can use either negative lookahead or negative lookbehind, regex speed is the same, Apache supports both

\"(.+(?<!-gzip))\"       #using negative lookbehind
\"((?:.(?!-gzip\"))+)\"  #using negative lookahead

Test cases:

    "2e2-5388f9f70c580-afeg"
    "2e2-5388f9f70c580-gzin"
    "2e2-5388f9f70c580-gzipd"
    "2e2-5388f9f70c580-gzip"
    "2e2-5388f9f70c580gzip"

Copy-Paste this code into Apache .conf

SetEnvIf           If-None-Match "-gzip\"$" request_etag=gzip
RequestHeader edit If-None-Match "(.+)-gzip\"$" "$1\""
Header edit        ETag     "(.+(?<!-gzip))\"$" "$1-gzip\"" "expr=reqenv('request_etag') == 'gzip' || resp('Content-Encoding') == 'gzip'"


I personally use the following code, that strips '-gzip' part initially if it's a gzip response, and doesn't reappend it, so the browser will never send a '-gzip' 'If-None-Match' header.

Header edit ETag "(.+)-gzip\"$" "$1\"" "expr=resp('Content-Encoding') == 'gzip'"
Community
  • 1
  • 1
Stalingrad
  • 159
  • 1
  • 8
  • The problem still exists in Apache 2.4.25. The 3 line code above helped me but I needed to enable `headers_module`. – ilhan Dec 26 '16 at 12:46
2

I know this is a very old question, but it appears there's a more up-to-date answer.

To have Apache not append the -gzip suffix, you must use the DeflateAlterETag directive with a value of NoChange.

See the documentation for this here: http://httpd.apache.org/docs/trunk/mod/mod_deflate.html#deflatealteretag

Sandy Chapman
  • 11,133
  • 3
  • 58
  • 67
  • Contrary to what the linked page says, this is not available on apache 2.4. (probably trunk/2.5 only). `Remove` would probably be the saner option anyway. – Stefan Aug 21 '23 at 08:42
0

Maybe you use a (squid) proxy which manipulates the HTTP Requests?

powtac
  • 40,542
  • 28
  • 115
  • 170