View Full Version : Images + caching

07-08-2005, 01:27 AM
I'm trying to force the browsers to cache the images I'm displaying through a PHP script.

So far, I have this... and it's not quite working. For some reason the browser will redownload them every couple times, instead of every 3 days:

(BTW, ob_get_contents() holds the image data.)

$expires = 60 * 60 * 24 * 3;
$exp_gmt = gmdate("D, d M Y H:i:s", time() + $expires )." GMT";
$mod_gmt = gmdate("D, d M Y H:i:s", time() + (3600 * -5 * 24 * 365) )." GMT";

@header("Content-type: image/jpg");
@header("Content-Disposition: inline; filename=purrun.jpg");
@header("Expires: {$exp_gmt}");
@header("Last-Modified: {$mod_gmt}");
@header("Cache-Control: public, max-age={$expires}");
@header("Pragma: !invalid");
@header("Content-Length: {$size}");
@header("ETag: " . md5( ob_get_contents() ) );

Anyone mind giving me a hand? Thanks.

07-08-2005, 04:33 PM
It's the curly braces ( {} ).
Using double quotes ( "" ) automatically parses variables defined with $. There's no need to put the braces in. The header is sent as Expires: {Mon, 26 Jul 1997 05:00:00 GMT}, and the browser becomes confused by the curly braces and ignores the Expires header.
/edit: could be wrong about what this does. In any case, the braces are unnecessary.

P.S. silencing everything is silly. If you must, define a blank error-handler like so:

function errorHandler ($errno, $errstr, $errfile, $errline, $errcontext) {

... or, better yet, actually fix the errors.

07-08-2005, 04:40 PM
I'll check on that. The curly braces were part of the original script I saw for caching, so I decided to keep 'em.

I also changed it to Cache-Control: public, max-age=(something, must-revalidate... seems to be working better now, though I'm not completely sure.


07-08-2005, 04:55 PM
You can check the headers by opening a command prompt/shell and telneting to your web server, then requesting the page. Like so:

> telnet
telnet> open myhost.com 80

Trying x.x.x.x...
Connected to myhost.com (x.x.x.x).

window waits for input
GET /path/mypage.php HTTP/1.1
Accept: */*
Host: myhost.com
Connection: Close
two linebreaks

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Date: Fri, 08 Jul 2005 16:48:29 GMT
Content-Type: text/html; charset=iso-8859-1
Server: Apache/1.3.33 (Unix) mod_auth_passthrough/1.8 mod_log_bytes/1.2 mod_bwlimited/1.4 PHP/4.3.10 FrontPage/ mod_ssl/2.8.22 OpenSSL/0.9.6b
X-Powered-By: PHP/4.3.10

Text in bold is user input, text in italic is comments by me, ignore. Headers obviously won't be the same as from my server.

You can also get various browser plugins, such as the header monitor (https://addons.mozilla.org/extensions/moreinfo.php?application=firefox&id=575&&page=comments) for Firefox.
Alternatively, you can use a packet sniffer. But if you can type fast enough to avoid your server disconnecting you, telnet's easiest and looks coolest if you've got someone looking over your shoulder :p

07-08-2005, 06:33 PM
It's the curly braces ( {} ).
Using double quotes ( "" ) automatically parses variables defined with $. There's no need to put the braces in.Whilst you're correct that the braces aren't necessary, you're wrong in that they are the problem. The braces are part of the variable expansion scheme, and are sometimes very necessary. See the section on complex expansion syntax (http://www.php.net/manual/en/language.types.string.php#language.types.string.parsing.complex) in the PHP documentation (http://www.php.net/manual/en/).

As for the actual problem, I can't really pin anything specific down but I haven't tried experimenting yet (been distracted :(). There are a few minor issues, though. Some may have an effect, some certainly won't.

header("Content-type: image/jpg");The MIME type is wrong. This should be:

header('Content-Type: image/jpeg');

header("Content-Disposition: inline; filename=purrun.jpg");A disposition type of inline has no meaning in HTTP. This header should be removed.

$mod_gmt = gmdate("D, d M Y H:i:s", time() + (3600 * -5 * 24 * 365) )." GMT";
header("Last-Modified: {$mod_gmt}");The last modified date shouldn't vary unless the resource actually changes. That the date is in the past doesn't matter - the variation still invalidates the resource. Use a fixed date.

header("Cache-Control: public, max-age={$expires}");Using the public directive is unnecessary. Using the max-age directive tends to imply public anyway.

After reading the above, you might think that the obvious cause would be the changing last modified date, but it really shouldn't matter. As the expiration information will indicate a freshly cached copy, the browser shouldn't get as far as trying to revalidate.

Are there any particular patterns to this behaviour? Is it a specific browser that keeps downloading, and do you know if the server is sending a complete response, or perhaps just 304 Not Modified?

Cache-Control: public, max-age=(something, must-revalidateYou don't want to send the must-revalidate directive. It won't harm cache behaviour as such, but it doesn't provide the benefits you're hoping for. Read RFC 2616 (http://www.faqs.org/rfcs/rfc2616.html) for more information (search for 'must-revalidate' in section 14.9.4).


07-11-2005, 04:17 AM
Regarding the ...

header("Content-Disposition: inline; filename=purrun.jpg");

...part, I added it so people have a "nice" default filename other than random gibberish (long story).

Since the original posting, I decided to add an image cache and use that last modified date and base everything on that, instead of "random" values - somehow that helped.

Thanks for all the help.

@Twey: nothing beats telnet w/ a green-on-black theme. "Follow the white rabbit" :p

07-12-2005, 06:10 PM
Darn right :p