RD2 launched our new site yesterday. One of my interests was in building RESTful URI for the website. The site is very minimal and only has sections for about, work and contact. Inside of work we break it down by client and then by section number. You end up with URI like work/entrust/1. The work portfolio section is built out with a PHP file. This creates the problem of a non-cacheable page.
Initially I set the page up to build with a simple include.
include('includes/portfolio/' . $company_list[$current_company]['copy_dir'] . '/' . $current_section . ".php");
?>
I decided to replace that with my own caching_include
function. The first thing I noticed is that I’m using include
to read the text files. I don’t need PHP to do a full include and parse of the file each time. I switched to using fpassthru
instead of include
.
The next thing I do is generate an ETag or entity tag for the page. I use the filepath and the file modification time to generate the ETag. Since I’m using the file’s modification time my ETag will change whenever the file does. This will make sure the browser doesn’t keep displaying a cached version even after I’ve made modifications.
I recommend using the Live HTTP Headers extension with Firefox for testing this out. You may also need to look into using output buffering if your headers are not being sent.
Here is the new caching_include
function I put together to more intelligent handly building out my templates.
function caching_include($file) {
$headers = apache_request_headers();
// Get the modification timestamp
list(,,,,,,,,,$lastModified) = stat($file);
// Build our entity tag
$eTag = "ci-".dechex(crc32($file.$lastModified));
if ((strpos($headers['If-None-Match'], "$eTag")) &&
(gmstrftime("%a, %d %b %Y %T %Z", $lastModified) == $headers['If-Modified-Since'])) {
// They already have an up to date copy so tell them
header('HTTP/1.1 304 Not Modified');
header('Cache-Control: private');
header("Pragma: ");
header('Expires: ');
header('Content-Type: ');
header('ETag: "'.$eTag.'"');
exit;
} else {
// We have to send them the whole page
$fh = fopen($file,'rb');
header('Cache-Control: private');
header('Pragma: ');
header('Expires: ');
header('Last-Modified: '.gmstrftime("%a, %d %b %Y %T %Z",$lastModified));
header('ETag: "'.$eTag.'"');
fpassthru($fh);
}
}
Dude! There’s a much saner way to get the modified time of the main file (works even if you *include* the function):
header(“Last-Modified: ” . date(“D, d M Y H:i:s”, getlastmod()));
It pays to Read The Fucking Manual.
Cheers,
-Hope-
I didn’t realize date() was GMT, I thought it took timezone into account. So yep, that would work too.
I wonder if there is a performance benefit to choosing one over the other.
header(“Last-Modified: “.gmdate(“D, d M Y H:i:s”,getlastmod()).” GMT”);
Thanks a lot. This is the only solution that worked for me trying to make the browser cache css files that were delivered by php.
The simple solution posted in the comments does have no effect for these files. Etags rule!
Thanks. But Actually i want to know that can we use etag example as given above for dynamic site. I mean to say that those files in which i m using php codes for website. If this possible then plz send me an examle.
Thanks a lot for this script Matt, it should be very useful.
header(“Last-Modified: ” . date(“r”, getlastmod()));
Thanks. This article helped. But one thing I found is a more portable way to get the If-None-Match request header is through $_SERVER['HTTP_IF_NONE_MATCH'].