FREE THOUGHT · FREE SOFTWARE · FREE WORLD

Speed Up Sites with php Caching

This explains the basic methods to get started caching with php headers


Article: http://www.w3.org/2005/MWI/BPWG/techs/CachingWithPhp

// $lastModifiedDate must be a GMT Unix Timestamp
// You can use gmmktime(...) to get such a timestamp
// getlastmod() also provides this kind of timestamp for the last
// modification date of the PHP file itself
function cacheHeaders($lastModifiedDate) {
  if ($lastModifiedDate) {
    if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $lastModifiedDate) {
      if (php_sapi_name()=='CGI') {
        Header("Status: 304 Not Modified");
      } else {
        Header("HTTP/1.0 304 Not Modified");
      }
      exit;
    } else {
      $gmtDate = gmdate("D, d M Y H:i:sGMT",$lastModifiedDate);
      header('Last-Modified: '.$gmtDate);
    }
  }
}

// This function uses a static variable to track the most recent
// last modification time
function lastModificationTime($time=0) {
  static $last_mod ;
  if (!isset($last_mod) || $time > $last_mod) {
    $last_mod = $time ;
  }
  return $last_mod ;
}

implementing caching and 304 Not Modified handling

BTW if you are implementing sending gziped output it might be a good time to think about implementing caching and 304 Not Modified handling as well. It is really easy.

<?php
/*
 A simple caching, zipping and If-Modified
 handling script by Andreas Friedrich
 $Id: cache.php,v 1.1 2002/07/16 14:45:06 af Exp $
 $Name: $
*/
#
$WEB_DEV_CACHE_DIR = '/var/www/html/www.ecotur.de//cache';

Check whether the UA sending the request supports gzip.

$ACCEPT_ENCODING = $_SERVER['HTTP_ACCEPT_ENCODING'];
if (strpos($ACCEPT_ENCODING, 'gzip')!== false) $enc = 'gzip';
if (strpos($ACCEPT_ENCODING, 'x-gzip')!== false) $enc = 'x-gzip';

Build a unique filename for the requested file. You can use caching for slightly dynamic pages as well. Just include the variables that determine what is output in the array below. Imagine a site that serves output depending on the UA string in the HTTP request header. For requests to a given URL we would need to store a file for each different UA in our cache.

$cached_file = '/var/www/html/www.ecotur.de//cache/'
. md5(implode('',
       array($_SERVER['REQUEST_URI'],
          $sessionid,
          $QUERY_STRING,
          $theme,
          ($sm? 'true' : 'fals'),
          ($ns4? 'true' : 'fals'),
          ($ns6? 'true' : 'fals'),
          ($moz? 'true' : 'fals'),
          ($op? 'true' : 'fals'),
          ($ie? 'true' : 'fals'),
          ($ie4? 'true' : 'fals'),
          ($ie5? 'true' : 'fals'),
          ($ie6? 'true' : 'fals'),
          ($linux? 'true' : 'fals'),
          ($mac? 'true' : 'fals'),
          $page,
          $desired_language,
          ))) . '_' . $enc;

If the response needs to be handled by the main script as is the case if the UA send a cookie or posted some data or the cached file is invalid or does not exist we start output buffering and run the script. Then we write the output to disk.

if (count($_COOKIE)>0
  or $_SERVER['REQUEST_METHOD']=='POST'
  or (!is_valid($cached_file))
  or ($info = @stat($cached_file)) == false
  ) {
 /* If there's no file or it's invalid we generate the output */
 ob_start();
 ob_implicit_flush(0);
#
 include($file);
#
 write_contents2cache($cached_file, $enc);
}
If-Modified Handling If the UA sent a conditional request we check whether the cached file was changed since the time the UA supplied. If not we return a 'HTTP/1.0 304 Not Modified' status and exit the script.
/* Send 304 Not Modified if there were no
 changes since last request */
$last = strtotime($info['mtime']);
$cond = isset($_SERVER['http_if_modified_since'])?
$_SERVER['http_if_modified_since'] : 0;
#
if ($cond and $_SERVER['REQUEST_METHOD'] == 'GET'
  and strtotime($cond) >= $last) {
 header('HTTP/1.0 304 Not Modified');
 exit;
}
Now that we are sure a valid output file exists we send the appropriate header fields and then the actual file.
/* Output the file now that we are sure the file exists */
if ($enc) {
 header("Content-Encoding: $enc");
 $size = filesize($cached_file);
 header("Content-Length: $size");
}
#
readfile($cached_file);
After sending the file we decide whether we want to keep the cached version or not. If it is highly dynamic (.i.e. uses cookies or posted data or contains a session id) we delete it.
if (isset($sessionid)
  or isset($_COOKIE['authorid'])
  or $_SERVER['REQUEST_METHOD']=='POST'
  or count($_COOKIE)>0) @unlink($cached_file);
if ($local) @unlink($cached_file);
#
?>

It might be better to save the file to disk only after we decide we want to keep it. This will eliminate the time to access the hd and write and delete the file. Hope this helps and is useful. Andreas

Use PHP to force a conditional GET

By examining $_SERVER['HTTP_IF_MODIFIED_SINCE'] if it is available. Consider the following function:

function conditionalGET($timestamp) {
  $last-modified = gmdate("D, d M Y H:i:s GMT", $timestamp);
  $etag = '"'.md5($last-modified).'"';
  header("Last-Modified: $last-modified");
  header("ETag: $etag");
  $if-modified-since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : false;
  $if-none-match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : false;
  if(!$if-modified-since && !$if-none-match) return;
  if($if-none-match && $if-none-match != $etag) return;
  if($if-modified-since && $if-modified-since != $last-modified) return;
  header("HTTP/1.0 304 Not Modified");
  exit;
}

To use the function, work out the timestamp for when the document was last modified and call the function with: conditionalGET($timestamp);

Optimization

 

 

Comments