Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

screenshot

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);
}

?>
like image 662
Alexander Farber Avatar asked Jun 01 '12 08:06

Alexander Farber


People also ask

What is if-modified-since header?

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.

What are the If-modified-since and if none match header used for?

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.

Do you see an if-modified-since line in this HTTP Get request?

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.

What HTTP status code means that the requested file was found but has not changed since date provided in the IF-modified-since header?

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.


2 Answers

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.

like image 101
Saneem Avatar answered Oct 16 '22 22:10

Saneem


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.

like image 25
Ja͢ck Avatar answered Oct 17 '22 00:10

Ja͢ck