I am using a PHP script to serve files.
I would like to be able to send back a 304
not modified header in my http response if the file has not changed since the client last downloaded it. This seems to be a feature in Apache (and most other web servers), but I have no clue how this can be implemented through PHP.
I have heard of using $_SERVER['HTTP_IF_MODIFIED_SINCE']
, but this variable does not seem to appear in my $_SERVER
super array.
My question is not how to return a 304
header, but how to know that one should be returned.
Edit: The problem is that my $_SERVER['HTTP_IF_MODIFIED_SINCE']
is not set. This is the content of my .htaccess
file:
ExpiresActive On
ExpiresByType image/jpeg "modification plus 1 month"
ExpiresByType image/png "modification plus 1 month"
ExpiresByType image/gif "modification plus 1 month"
Header append Cache-Control: "must-revalidate"
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond $1 !^(controller\.php)
RewriteRule (.*\.jpg|.*\.png|.*\.gif) controller.php/$1
</IfModule>
HTTP_IF_MODIFIED_SINCE
still does not appear in the $_SERVER
super array.
HTTP_IF_MODIFIED_SINCE
is the right way to do it. If you aren't getting it, check that Apache has mod_expires
and mod_headers
enabled and working properly. Borrowed from a comment on PHP.net:
$last_modified_time = filemtime($file);
$etag = md5_file($file);
// always send headers
header("Last-Modified: ".gmdate("D, d M Y H:i:s", $last_modified_time)." GMT");
header("Etag: $etag");
// exit if not modified
if (@strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified_time ||
@trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag) {
header("HTTP/1.1 304 Not Modified");
exit;
}
// output data
This article will answer all your questions on caching
I found that adding
RewriteRule .* - [E=HTTP_IF_MODIFIED_SINCE:%{HTTP:If-Modified-Since}]
RewriteRule .* - [E=HTTP_IF_NONE_MATCH:%{HTTP:If-None-Match}]
To the bottom of my htaccess file (below all rewriterule) worked.
$_SERVER['HTTP_IF_MODIFIED_SINCE']
is usually empty when register_globals
is off.
Check whether that's the case, and if so try getenv('HTTP_IF_MODIFIED_SINCE')
There are also some others parameters to check .. in my case I didn't had both of those headers :
$_SERVER['HTTP_IF_NONE_MATCH'] && $_SERVER['HTTP_IF_MODIFIED_SINCE']
which are required to return a proper 304 header, as my system clock was a little late, It'll interpret those pages as expiring in the future, then not sending those values at all
Also check that header which is returned by apache, or at least override it to a bigger value
Cache-Control: max-age=3600
As it won't send previous headers if
Last-Modified previous sent header < ( NOW - 3600 )
So in my case I've set this pretty handy function
function lastModified($file){
$x=filemtime($file);
while($x>time())$x-=86000;}#reduce by one day if touched in future date
$date=gmdate('D, j M Y H:i:s',$x).' GMT';
header('Cache-Control: max-age=86000',1);
if($_SERVER['HTTP_IF_NONE_MATCH'] == $x || $_SERVER['HTTP_IF_MODIFIED_SINCE']==$date){
header('HTTP/1.1 304 Not Modified',1,304);die;}
header('Etag: '.$x,1);header('Last-Modified: '.$date,1);
}
I had this problem and it turned out to simply be that I had Firebug open. This has an option under the Net tab "Disable Browser Cache" that is ticked by default. There is a similar option in Chrome's developer tools, one of the tick boxes on the bar under the menu bar.
Unticking these options resulted in the browser correctly sending HTTP_IF_MODIFIED_SINCE
and everything working fine after all (even with Firebug or Chrome Dev Tools open).
Note that $_SERVER["HTTP_IF_NONE_MATCH"] can contain quotes and -gzip suffix.
$server_etag = str_replace("-gzip", "", str_replace('"', '', stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])));
if ($server_etag == $etag) ...