Caching HTTP responses when they are dynamically created by PHP

Serving huge or many auxiliary files with PHP is not exactly what it’s made for.

Instead, look at X-accel for nginx, X-Sendfile for Lighttpd or mod_xsendfile for Apache.

The initial request gets handled by PHP, but once the download file has been determined it sets a few headers to indicate that the server should handle the file sending, after which the PHP process is freed up to serve something else.

You can then use the web server to configure the caching for you.

Static generated content

If your content is generated from PHP and particularly expensive to create, you could write the output to a local file and apply the above method again.

If you can’t write to a local file or don’t want to, you can use HTTP response headers to control caching:

Expires: <absolute date in the future>
Cache-Control: public, max-age=<relative time in seconds since request>

This will cause clients to cache the page contents until it expires or when a user forces a page reload (e.g. press F5).

Dynamic generated content

For dynamic content you want the browser to ping you every time, but only send the page contents if there’s something new. You can accomplish this by setting a few other response headers:

ETag: <hash of the contents>
Last-Modified: <absolute date of last contents change>

When the browser pings your script again, they will add the following request headers respectively:

If-None-Match: <hash of the contents that you sent last time>
If-Modified-Since: <absolute date of last contents change>

The ETag is mostly used to reduce network traffic as in some cases, to know the contents hash, you first have to calculate it.

The Last-Modified is the easiest to apply if you have local file caches (files have a modification date). A simple condition makes it work:

if (!file_exists('cache.txt') || 
    filemtime('cache.txt') > strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
    // update cache file and send back contents as usual (+ cache headers)
} else {
    header('HTTP/1.0 304 Not modified');
}

If you can’t do file caches, you can still use ETag to determine whether the contents have changed meanwhile.

Leave a Comment