Dec 3, 2009

Conditional caching: several approaches

Cache integrityLast month we talked about various caching layers last month. The latest 'line of defense' is conditional caching.

What is Conditional Caching?

Browser usually has a lot of resources in its local cache. Some of them can be expired but browser can check if they can be used once more. So there is a way (frankly speaking two different ways) to check if a resource can be used once more.

First we can use Last-Modified (specified in HTTP/1.0) and its pair — If-Modified-Since. Server sends header Last-Modified with the resource modification date. Browser can request if resource was changed since the last request and send this date back with If-Modified-Since header. If there were no modifications server responses with 304-answer.

There is almost the same situation with ETag and If-None-Match (or If-Match) headers. Expect the only thing that Entity Tag can be any string (date, set of numbers, file name, etc). So this allows you to define it any way. But ETag belongs to HTTP/1.1 specification.

Benefits

With 304-answers server actually doesn't send any content to browser so you save transfer time for these resources.

If can be also useful for a kind of dynamic content which can't be cached for a long time but can't be changed often. So browser re-requests such content (if cache is expired) but receives answer 'Not modified'.

Also if helps with pages' reload - when visitor presses Ctrl+R in his/her browser the latter must request from the server all resources. So such kind of refresh can be made faster with conditional caching.

Practical examples

Apache server usually sends ETag among Last-Modified. There is mod_headers that is responsible for such behavior (and it seems for 304-answers). You can add ETag header (which indicates file modification time only) this way:

FileETag MTime

You can also unset each of these headers:

Header unset Last-Modified
Header unset ETag

With PHP you can send the equivalent of these headers by:

@date_default_timezone_set(@date_default_timezone_get());
header("Last-Modified: " . gmdate("D, d M Y H:i:s", $time));
header("ETag: \"" . md5(gmdate("D, d M Y H:i:s", $time)) . "\"");

If you want to emulate 'classic' ETag which Apache server sends by default you need to use:

header("ETag: \"" . dec2hex(@fileinode($filename)) . '-' .
dec2hex(@filesize($filename)) . '-' . dec2hex($mtime) . "\"");

where dec2hec is a helper to convert numbers for correct conversion numbers from decimal to heximal.

Issues

First of all you need to send different conditional headers with compressed and not compressed content. Generic approach here is to add '-gzip' to the end of the ETag (there is nothing to do with Last-Modified so it's a bit less usable).

Then you need to make such headers equal through all servers you use to server content. Because common ETag header in Apache includes information about inode (server-related, but not actually file-related), so it must be eliminated or replaced.

Then there is information (not approved yet) about excessive requests from browsers with conditional headers. Please be careful with this.

For static resources Web Optimizer unsets Last-Modified header for all static resources and sets ETag based on modification time. For dynamic ones it (if HTML documents are cached) sets ETag based on content hash and Last-Modified with static PHP proxy.

No comments:

Post a Comment