Handling If-modified-since header in a PHP-script
I have a PHP script which is called with an ?img= parameter.
The value for that parameter is an (urlencoded) URL of an image.
My script checks, if that image is already stored at my server.
If not - it downloads it. After that it optionally resizes the image and sends it to STDOUT, i.e. back to the requesting browser, prepended with Content-Type and Last-modified headers:
Connection:close
Content-Type:image/jpeg
Date:Fri, 01 Jun 2012 08:28:30 GMT
Last-Modified:Fri, 01 Jun 2012 08:02:44 GMT
Server:Apache/2.2.15 (CentOS)
Transfer-Encoding:chunked
X-Powered-By:PHP/5.3.3
This is needed to workaround some crossdomain issues and works well for me since over a year:
However I'd like to add functionality to handle the incoming If-Modified-since header - to send a Not Modified 304 response.
My questions are:
1) Is that even possible in PHP, when run in Apache?
2) How to handle (i.e. parse and produce) the dates best in PHP here?
Bonus question) How to add a Content-Length header for the resized images?
My code is below (I've omitted the CURL-downloading part):
<?php
define('CACHE_DIR', '/var/www/cached_avatars/');
$img = urldecode($_GET['img']);
$cached = CACHE_DIR . md5($img);
# omitted downloading part for brevity
$readfh = fopen($cached, 'rb');
if ($readfh) {
flock($readfh, LOCK_SH);
$size = getimagesize($cached);
$w = $size[0];
$h = $size[1];
$type = $size[2];
$mime = $size['mime'];
# find the downscale factor to fit image into $maxw x $maxh
$scale = max($w / $maxw, $h / $maxh);
header('Content-Type: ' . $size['mime']);
header('Last-Modified: ' . gmdate('D, d M Y H:i:s T', filemtime($cached)));
$length = filesize($cached);
$buf = fread($readfh, $length);
fclose($readfh);
# the image is smaller than $maxw x $maxh, do not scale up
if ($scale <= 1) {
header('Content-Length: ' . $length);
print($buf);
return;
}
$tw = $w / $scale;
$th = $h / $scale;
$image = imagecreatefromstring($buf);
$thumb = imagecreatetruecolor($tw, $th);
imagecopyresampled($thumb, $image, 0, 0, 0, 0, $tw, $th, $w, $h);
imagedestroy($image);
# How to add Content-Length here, after image resizing?
if (IMAGETYPE_JPEG == $type)
imagejpeg($thumb, null, 75);
else if (IMAGETYPE_PNG == $type)
imagepng($thumb, null, 9);
else if (IMAGETYPE_GIF == $type)
imagegif($thumb, null);
imagedestroy($thumb);
}
?>
This is definitely possible in PHP!
When the browser checks if there were modifications, it sends an If-Modified-Since
header; in PHP this value would be set inside $_SERVER['HTTP_IF_MODIFIED_SINCE']
.
To decode the date/time value (encoded using rfc822 I believe), you can just use strtotime()
, so:
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= filemtime($localFileName))
{
header('HTTP/1.0 304 Not Modified');
exit;
}
Explanation: if the If-Modified-Since
header is sent by the browser AND the date/time is at least the modified date of the file you're serving, you write the "304 Not Modified" header and stop.
Otherwise, the script continues as per normal.
I recently had to use this feature (serving image via PHP) in order to make images visible only for registered users. Jack's code was helpful, but I had to do a few hacks for it to work perfectly. Thought I should share this one.
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= filemtime($path_to_image))
{
header('HTTP/1.0 304 Not Modified');
header("Cache-Control: max-age=12096000, public");
header("Expires: Sat, 26 Jul 2015 05:00:00 GMT");
header("Pragma: cache");
exit;
}else{
header("Content-type: image/jpeg");
header("Cache-Control: max-age=12096000, public");
header("Expires: Sat, 26 Jul 2015 05:00:00 GMT");
header("Pragma: cache");
echo file_get_contents($path_to_image);
}
In short, the script returns Not Modified if it's a browser request HTTP_IF_MODIFIED_SINCE
. Otherwise, the image is served with appropriate headers and expiry dates.