How to force a web browser NOT to cache images

Viewed 247863

Background

I am writing and using a very simple CGI-based (Perl) content management tool for two pro-bono websites. It provides the website administrator with HTML forms for events where they fill the fields (date, place, title, description, links, etc.) and save it. On that form I allow the administrator to upload an image related to the event. On the HTML page displaying the form, I am also showing a preview of the picture uploaded (HTML img tag).

The Problem

The problem happens when the administrator wants to change the picture. He would just have to hit the "browse" button, pick a new picture and press ok. And this works fine.

Once the image is uploaded, my back-end CGI handles the upload and reloads the form properly.

The problem is that the image shown does not get refreshed. The old image is still shown, even though the database holds the right image. I have narrowed it down to the fact that the IMAGE IS CACHED in the web browser. If the administrator hits the RELOAD button in Firefox/Explorer/Safari, everything gets refreshed fine and the new image just appears.

My Solution - Not Working

I am trying to control the cache by writing a HTTP Expires instruction with a date very far in the past.

Expires: Mon, 15 Sep 2003 1:00:00 GMT

Remember that I am on the administrative side and I don't really care if the pages takes a little longer to load because they are always expired.

But, this does not work either.

Notes

When uploading an image, its filename is not kept in the database. It is renamed as Image.jpg (to simply things out when using it). When replacing the existing image with a new one, the name doesn't change either. Just the content of the image file changes.

The webserver is provided by the hosting service/ISP. It uses Apache.

Question

Is there a way to force the web browser to NOT cache things from this page, not even images?

I am juggling with the option to actually "save the filename" with the database. This way, if the image is changed, the src of the IMG tag will also change. However, this requires a lot of changes throughout the site and I rather not do it if I have a better solution. Also, this will still not work if the new image uploaded has the same name (say the image is photoshopped a bit and re-uploaded).

20 Answers

Armin Ronacher has the correct idea. The problem is random strings can collide. I would use:

<img src="picture.jpg?1222259157.415" alt="">

Where "1222259157.415" is the current time on the server.
Generate time by Javascript with performance.now() or by Python with time.time()

Simple fix: Attach a random query string to the image:

<img src="foo.cgi?random=323527528432525.24234" alt="">

What the HTTP RFC says:

Cache-Control: no-cache

But that doesn't work that well :)

You may write a proxy script for serving images - that's a bit more of work though. Something likes this:

HTML:

<img src="image.php?img=imageFile.jpg&some-random-number-262376" />

Script:

// PHP
if( isset( $_GET['img'] ) && is_file( IMG_PATH . $_GET['img'] ) ) {

  // read contents
  $f = open( IMG_PATH . $_GET['img'] );
  $img = $f.read();
  $f.close();

  // no-cache headers - complete set
  // these copied from [php.net/header][1], tested myself - works
  header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Some time in the past
  header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); 
  header("Cache-Control: no-store, no-cache, must-revalidate"); 
  header("Cache-Control: post-check=0, pre-check=0", false); 
  header("Pragma: no-cache"); 

  // image related headers
  header('Accept-Ranges: bytes');
  header('Content-Length: '.strlen( $img )); // How many bytes we're going to send
  header('Content-Type: image/jpeg'); // or image/png etc

  // actual image
  echo $img;
  exit();
}

Actually either no-cache headers or random number at image src should be sufficient, but since we want to be bullet proof..

When uploading an image, its filename is not kept in the database. It is renamed as Image.jpg (to simply things out when using it).

Change this, and you've fixed your problem. I use timestamps, as with the solutions proposed above: Image-<timestamp>.jpg

Presumably, whatever problems you're avoiding by keeping the same filename for the image can be overcome, but you don't say what they are.

You must use a unique filename(s). Like this

<img src="cars.png?1287361287" alt="">

But this technique means high server usage and bandwidth wastage. Instead, you should use the version number or date. Example:

<img src="cars.png?2020-02-18" alt="">

But you want it to never serve image from cache. For this, if the page does not use page cache, it is possible with PHP or server side.

<img src="cars.png?<?php echo time();?>" alt="">

However, it is still not effective. Reason: Browser cache ... The last but most effective method is Native JAVASCRIPT. This simple code finds all images with a "NO-CACHE" class and makes the images almost unique. Put this between script tags.

var items = document.querySelectorAll("img.NO-CACHE");
for (var i = items.length; i--;) {
    var img = items[i];
    img.src = img.src + '?' + Date.now();
}

USAGE

<img class="NO-CACHE" src="https://upload.wikimedia.org/wikipedia/commons/6/6a/JavaScript-logo.png" alt="">

RESULT(s) Like This

https://example.com/image.png?1582018163634

Add a time stamp <img src="picture.jpg?t=<?php echo time();?>">

will always give your file a random number at the end and stop it caching

With the potential for badly behaved transparent proxies in between you and the client, the only way to totally guarantee that images will not be cached is to give them a unique uri, something like tagging a timestamp on as a query string or as part of the path.

If that timestamp corresponds to the last update time of the image, then you can cache when you need to and serve the new image at just the right time.

I assume original question regards images stored with some text info. So, if you have access to a text context when generating src=... url, consider store/use CRC32 of image bytes instead of meaningless random or time stamp. Then, if the page with plenty of images is displaying, only updated images will be reloaded. Eventually, if CRC storing is impossible, it can be computed and appended to the url at runtime.

Ideally, you should add a button/keybinding/menu to each webpage with an option to synchronize content.

To do so, you would keep track of resources that may need to be synchronized, and either use xhr to probe the images with a dynamic querystring, or create an image at runtime with src using a dynamic querystring. Then use a broadcasting mechanism to notify all components of the webpages that are using the resource to update to use the resource with a dynamic querystring appended to its url.

A naive example looks like this:

Normally, the image is displayed and cached, but if the user pressed the button, an xhr request is sent to the resource with a time querystring appended to it; since the time can be assumed to be different on each press, it will make sure that the browser will bypass cache since it can't tell whether the resource is dynamically generated on the server side based on the query, or if it is a static resource that ignores query.

The result is that you can avoid having all your users bombard you with resource requests all the time, but at the same time, allow a mechanism for users to update their resources if they suspect they are out of sync.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="mobile-web-app-capable" content="yes" />        
    <title>Resource Synchronization Test</title>
    <script>
function sync() {
    var xhr = new XMLHttpRequest;
    xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {            
            var images = document.getElementsByClassName("depends-on-resource");

            for (var i = 0; i < images.length; ++i) {
                var image = images[i];
                if (image.getAttribute('data-resource-name') == 'resource.bmp') {
                    image.src = 'resource.bmp?i=' + new Date().getTime();                
                }
            }
        }
    }
    xhr.open('GET', 'resource.bmp', true);
    xhr.send();
}
    </script>
  </head>
  <body>
    <img class="depends-on-resource" data-resource-name="resource.bmp" src="resource.bmp"></img>
    <button onclick="sync()">sync</button>
  </body>
</html>

All the Answers are valid as it works fine. But with that, the browser also creates another file in the cache every time it loads that image with a different URL. So instead of changing the URL by adding some query params to it.

So, what we can do is we can update the browser cache using cache.put

caches.open('YOUR_CACHE_NAME').then(cache => {
  const url = 'URL_OF_IMAGE_TO_UPDATE'
  fetch(url).then(res => {
    cache.put(url, res.clone())
  })
})

cache.put updates the cache with a new response.

for more: https://developer.mozilla.org/en-US/docs/Web/API/Cache/put

I've found Chrome specifically tries to get clever with the URL arguments solution on images. That method to avoid cache only works some of the time. The most reliable solution I've found is to add both a URL argument (E.g. time stamp or file version) AND also change the capitalisation of the image file extension in the URL.

<img src="picture.jpg">

becomes

<img src="picture.JPG?t=current_time">

Best solution is to provide current time at the end of the source href like <img src="www.abc.com/123.png?t=current_time">

this will remove the chances of referencing the already cache image. To get the recent time one can use performance.now() function in jQuery or javascript.

Related