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);
}
?>
The If-Modified-Since request HTTP header makes the request conditional: the server sends back the requested resource, with a 200 status, only if it has been last modified after the given date.
The If-Modified-Since header is used to specify the time at which the browser last received the requested resource. The If-None-Match header is used to specify the entity tag that the server issued with the requested resource when it was last received.
Now inspect the contents of the second HTTP GET request from your browser to the server. Do you see an “IF-MODIFIED-SINCE:” line in the HTTP GET? If so, what information follows the “IF-MODIFIED-SINCE:” header? Answer: Yes.
If the status of a particular resource is 304 Not Modified, this means that the file has not changed and there is no need to download it again.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With