Bug 47580 - mod_cache sends 200 response instead of 304
Summary: mod_cache sends 200 response instead of 304
Status: REOPENED
Alias: None
Product: Apache httpd-2
Classification: Unclassified
Component: mod_cache (show other bugs)
Version: 2.2.25
Hardware: All All
: P2 normal (vote)
Target Milestone: ---
Assignee: Apache HTTPD Bugs Mailing List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2009-07-25 05:18 UTC by Nicholas Sherlock
Modified: 2017-03-24 20:20 UTC (History)
1 user (show)



Attachments
PHP script to demonstrate the problem (930 bytes, text/plain)
2009-07-25 05:18 UTC, Nicholas Sherlock
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Nicholas Sherlock 2009-07-25 05:18:51 UTC
Created attachment 24037 [details]
PHP script to demonstrate the problem

If you make a conditional request for a cached document, but the document is expired in the cache, mod_cache correctly passes on the conditional request to the backend. If the backend responds with a "304 Not Modified" response that indicates that the cached copy is still up to date, mod_cache serves the contents of the cache to the client with a 200 code. This is a standards-conforming valid way of responding.

But couldn't it just send a "304 Not Modified" code instead? RFC2616 14.26 says "instead, if the request method was GET or HEAD, the server SHOULD respond with a 304 (Not Modified) response, including the cache- related header fields (particularly ETag) of one of the entities that matched." The current behaviour unnecessarily sends a response body to the client. This ends up wasting bandwidth in the case where you press refresh on an unmodified object (at least in Firefox,) which sends these request headers:

If-None-Match="My ETag"
Cache-Control=max-age=0

I do not want the behaviour given by the "CacheIgnoreCacheControl yes" directive. I still want mod_cache to validate the request against the backend, but I don't want it to waste bandwidth by sending a 200 response code.

To test it, I have these cache-related lines in my virtual host definition:

CacheRoot C:/temp
CacheEnable disk /

My index.php is the attached file.

My web browser with an empty cache requests index.php, the (trimmed) response is:

Status=OK - 200
Date=Mon, 20 Jul 2009 07:16:05 GMT
Expires=Wed, 19 Aug 2009 07:16:05 GMT
Etag="ComputedETag"

The log performed by index.php indicates:

Mon, 20 Jul 2009 19:16:05 +1200 - Response: 200. Generated document.

So far so good. But now I press refresh in my web browser. This makes a conditional request for the document:

If-None-Match="ComputedETag"
Cache-Control=max-age=0

With the max-age of 0, the cache will be bypassed, which is the desired behaviour. The cache passes this conditional request onto the backend, and the backend logs it:

Mon, 20 Jul 2009 19:16:12 +1200 - Response: 304 Not Modified

So the backend is trying to tell the client that it already has an up-to-date body. But the response sent to the client by mod_cache is:

Status=OK - 200
Date=Mon, 20 Jul 2009 07:16:12 GMT
Etag="ComputedETag"
Expires=Wed, 19 Aug 2009 07:16:12 GMT

My Apache configuratiion is:

Apache/2.2.11 (Win32) DAV/2 mod_ssl/2.2.11 OpenSSL/0.9.8i SVN/1.6.3 PHP/5.3.0
Comment 1 Ruediger Pluem 2009-07-25 12:37:43 UTC
This is a bug in php or more specific in the httpd module version of php. In sapi_apache2.c::php_apache_request_ctor (line 463 for php 5.3.0) it sets r->no_local_copy to 1 for an unknown reason which causes to return a 200 instead of a 304. If you comment this line everything works as expected by you.
httpd itself only set this struct member to 1 for

subrequests
error pages

in both cases it is not desired that even conditional requests return a 304.
For subrequests as they only deliver fragments of a page that is processed internally and for error pages this is obvious.
In order to fix your problem do one of the following things:

1. Use the CGI/FASTCGI version of PHP.
2. Comment the line in the PHP code as described above (no idea which further sideeffects this has as I am not a php developer).
3. Open a bug report at bugs.php.net to get this fixed.
Comment 2 Nicholas Sherlock 2009-07-30 22:52:38 UTC
For anyone following this bug, this is now PHP bug #49106:

http://bugs.php.net/bug.php?id=49106
Comment 3 Ajay Sindwani 2013-09-07 21:07:20 UTC
Confirming that this defect still exists in Apache 2.2.21 with mod_cache enabled.

Browser sends requests with If-None-Match headers that correspond with ETag for content.

mod_cache is configured to ignore all headers from request and response that could cause a cache miss.  mod_cache returns cache hit from the cache, in my test case disk cache, however also reproducible with a mem cache.  The cache hit despite matching the If-None-Match in the request returns with a response code of 200 and sends the full body in the response, instead of a terse/concise 304.

This has nothing to do with PHP which it looks like Nicholas Sherlock had originally attached to recreate the problem.  I can recreate the same problem with Fiddler.
Comment 4 Ajay Sindwani 2013-09-08 15:43:19 UTC
Workaround to this bug in mod_cache:
Disable ETags and If-None-Match headers for 304 checking
Instead use If-Modified-Since and Last-Modified headers.

#To deal with caches already in my users browser I used this config before hitting #mod_cache to ensure they don't get included in the request and confuse mod_cache
RequestHeader unset If-None-Match

#This cleans ETag headers from any apps downstream from my Apache incase they generated them.  
Header unset ETag

With the If-Modified-Since and Last-Modified headers mod_cache appears to be properly writing back files from its cache and also writing back 304 HTTP Status codes when serving files from its cache that already match with the ones on the browser.
Comment 5 Zwilla 2017-03-24 20:20:57 UTC
this solution works

RequestHeader unset If-None-Match
Header unset ETag

but the problem still exist on 2.2.25 Ubuntu 14.04

Thx for the solution above :)