Image: Browser caching.

Created on 6 Feb 2014  路  9Comments  路  Source: Intervention/image

I can't get browser caching to work properly with the laravel integration bundle.
The major problem is that the cached image (ImageCache) is not accessible as a file.
Is there already a proper solution for this situation?

Most helpful comment

Don't know if i'll help someone, but here is my implementation (Laravel 5.3).
It modifies the Reponse properly, instead of sending direct headers.

    function sendImage($response, $modified, $browserCache=60*60*24*7)
    {
        $lastModified = gmdate('D, d M Y H:i:s', $modified).' GMT';
        $eTag = '"' . md5($response->getContent()) . '"';
        $isModified = false;

        $ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
            ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE'])
            : false;
        $ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH'])
            ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])
            : false;

        if (!$ifModifiedSince && !$ifNoneMatch) {
            $isModified = true;
        } elseif ($ifNoneMatch && $ifNoneMatch !== $eTag) {
            $isModified = true;
        } else if ($ifModifiedSince && $ifModifiedSince != $lastModified) {
            $isModified = true;
        }

        if ($isModified) {
            return $response->withHeaders([
                'Cache-Control'  => 'private, max-age='.$browserCache,
                'Expires'        => gmdate('D, d M Y H:i:s', time()+$browserCache).' GMT',
                'Content-Length' => strlen($response->getContent()),
                'Last-Modified'  => $lastModified,
                'ETag'           => $eTag,
            ]);
        } else {
            return $response->setNotModified();
        }
    }

    // In your controller...
    $modified = 0;
    $imgResponse = Image::cache(function($image) use ($source, &$modified) {
        $image->make($source);
        $modified = array_key_exists('modified', $image->properties) ? $image->properties['modified'] : time();
    }, $browserCache, true)->response('jpg', $quality);

    return sendImage($imgResponse, $modified, $browserCache);

All 9 comments

The imagecache was intended just to bypass GD library operations and should actually not care about the HTTP response (browser caching).

Why not add Cache-Control header manually?

Could you give an example of a "proper" HTTP response header?

Because you need to know what file you're dealing with. Because of the Cache abstraction you can't "touch" the actual image.

A header could look something like this. (+ expires, etc.)

$headers = array(
            'Content-Disposition' => 'inline; filename="' . $name . '"',
            'Cache-Control' => 'must-revalidate',
            'Pragma' => 'public',
            'Etag' => $etag,
            'Content-Type' => $mime,
            'Content-Length' => $length,
        );

I'm afraid there is no quick solution. Since there is no image file by default, we only have image-data in cache-storage, which might be backed by Memcached, Redis or whatever is configured.

I think what's most important is the age of the data in cache. But I can't find methods of Illuminate/Cache to get information like this.

Thats what I thought. It would be a nice feature, thou.

This is my version for browser cache

<?php
use Intervention\Image\ImageManagerStatic as Image;

Image::configure([
    'driver' => class_exists('Imagick') ? 'imagick' : 'gd'
]);

$browserCache = 60*60*24*7;
$quality = 75;

function sendImage($output, $info, $browserCache) {
    $lastModified = gmdate('D, d M Y H:i:s', $info['modified']).' GMT';
    $eTag = '"' . $info['checksum'] . '"';
    $isModified = false;

    header('Cache-Control: private, max-age='. $browserCache);
    header('Expires: '.gmdate('D, d M Y H:i:s', time()+$browserCache).' GMT');
    header('Content-Length: ' . strlen($output));
    header('Last-Modified: ' . $lastModified);
    header('ETag: ' . $eTag);

    $ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
        ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE'])
        : false;
    $ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH'])
        ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])
        : false;

    if (!$ifModifiedSince && !$ifNoneMatch) {
        $isModified = true;
    } elseif ($ifNoneMatch && $ifNoneMatch !== $eTag) {
        $isModified = true;
    } else if ($ifModifiedSince && $ifModifiedSince != $lastModified) {
        $isModified = true;
    }

    if ($isModified) {
        echo $output;
    } else {
        header('HTTP/1.1 304 Not Modified');
    }
}

try {
    $info = [];
    $img = Image::cache(function($image) use ( $source, &$info) {
        $image->make($source);
        $info = [
            'checksum' =>  $image->checksum(),
            'modified' => array_key_exists('modified', $image->properties) ? $image->properties['modified'] : time()
        ];
    }, $browserCache, true);

    sendImage($img->response($img->mime, $quality), $info, $browserCache);
} catch (Exception $e) {
    header("HTTP/1.0 404 Not Found");
}

Thanks @vitalyrotari, I will look into this.

I would not use image->checksum() because the method is performance intensive. I would rather create a hash directly from the image data.

Thanks for tip @olivervogel.

Updated version

<?php

use Intervention\Image\ImageManagerStatic as Image;

Image::configure([
    'driver' => class_exists('Imagick') ? 'imagick' : 'gd'
]);

$browserCache = 60*60*24*7;
$quality = 75;

function sendImage($output, $modified, $browserCache) {
    $lastModified = gmdate('D, d M Y H:i:s', $modified).' GMT';
    $eTag = '"' . md5($output) . '"';
    $isModified = false;

    header('Cache-Control: private, max-age='. $browserCache);
    header('Expires: '.gmdate('D, d M Y H:i:s', time()+$browserCache).' GMT');
    header('Content-Length: ' . strlen($output));
    header('Last-Modified: ' . $lastModified);
    header('ETag: ' . $eTag);

    $ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
        ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE'])
        : false;
    $ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH'])
        ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])
        : false;

    if (!$ifModifiedSince && !$ifNoneMatch) {
        $isModified = true;
    } elseif ($ifNoneMatch && $ifNoneMatch !== $eTag) {
        $isModified = true;
    } else if ($ifModifiedSince && $ifModifiedSince != $lastModified) {
        $isModified = true;
    }

    if ($isModified) {
        echo $output;
    } else {
        header('HTTP/1.1 304 Not Modified');
    }
}

try {
    $modified = 0;
    $img = Image::cache(function($image) use ($source, &$modified) {
        $image->make($source);
        $modified = array_key_exists('modified', $image->properties) ? $image->properties['modified'] : time();
    }, $browserCache, true);

    sendImage($img->response($img->mime, $quality), $modified, $browserCache);
} catch (Exception $e) {
    header("HTTP/1.0 404 Not Found");
}

Don't know if i'll help someone, but here is my implementation (Laravel 5.3).
It modifies the Reponse properly, instead of sending direct headers.

    function sendImage($response, $modified, $browserCache=60*60*24*7)
    {
        $lastModified = gmdate('D, d M Y H:i:s', $modified).' GMT';
        $eTag = '"' . md5($response->getContent()) . '"';
        $isModified = false;

        $ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
            ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE'])
            : false;
        $ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH'])
            ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])
            : false;

        if (!$ifModifiedSince && !$ifNoneMatch) {
            $isModified = true;
        } elseif ($ifNoneMatch && $ifNoneMatch !== $eTag) {
            $isModified = true;
        } else if ($ifModifiedSince && $ifModifiedSince != $lastModified) {
            $isModified = true;
        }

        if ($isModified) {
            return $response->withHeaders([
                'Cache-Control'  => 'private, max-age='.$browserCache,
                'Expires'        => gmdate('D, d M Y H:i:s', time()+$browserCache).' GMT',
                'Content-Length' => strlen($response->getContent()),
                'Last-Modified'  => $lastModified,
                'ETag'           => $eTag,
            ]);
        } else {
            return $response->setNotModified();
        }
    }

    // In your controller...
    $modified = 0;
    $imgResponse = Image::cache(function($image) use ($source, &$modified) {
        $image->make($source);
        $modified = array_key_exists('modified', $image->properties) ? $image->properties['modified'] : time();
    }, $browserCache, true)->response('jpg', $quality);

    return sendImage($imgResponse, $modified, $browserCache);
Was this page helpful?
0 / 5 - 0 ratings

Related issues

DrDeath72 picture DrDeath72  路  4Comments

divdax picture divdax  路  7Comments

cherrycoding picture cherrycoding  路  3Comments

kamov picture kamov  路  6Comments

theofidry picture theofidry  路  5Comments