5

User generated images on my site are served up by giving them a src like this:

userImage.ashx?id={UserId}&type=avatar

In the response from the ashx file, I set the etag header. When a user uploads a new image, the etag changes.

If the browser has a file cached with an etag, it should send a request to the server with the If-None-Match header set to that etag whenever it needs to display that file. If the cached etag is the same as the current etag on the server, the server responds with Not Modified - 304. If the etag is different, the server responds with OK - 200 and starts sending the new file.

This is how it should work in theory. However, I have found that for some browsers (firefox and IE, untested on others) this is not the case. If the user navigates to a new page with cached, etagged images, these browsers simply use the image out of their cache without making a request. If the user then refreshes the page, the browser sends a request with the If-None-Match header set.

So my problem is this: a user updates one of their images, then navigates to a page displaying the image. Until the user presses refresh, the cached image will be displayed, even though it has a different etag to the new image. When the user presses refresh, the browser does a request with the If-None-Match header set, which triggers the server to send the new image.

Is it possible to fix this?

Example 200 response header:

Status=OK - 200
Date=Thu, 27 Oct 2011 14:37:31 GMT
Server=Microsoft-IIS/6.0
X-Powered-By=ASP.NET
X-AspNet-Version=4.0.30319
Transfer-Encoding=chunked
Cache-Control=public, max-age=86400
Etag="27/10/2011 13:23:30"
Content-Type=image/jpg

Example 304 header:

Status=Not Modified - 304
Connection=close
Date=Thu, 27 Oct 2011 14:39:12 GMT
Server=Microsoft-IIS/6.0
X-Powered-By=ASP.NET
X-AspNet-Version=4.0.30319
Cache-Control=public, max-age=86400

(Using last modfied date as etag as its more adaptable for later needs regarding compression etc.)

Oliver
  • 11,297
  • 18
  • 71
  • 121

2 Answers2

2

IIRC you can set how long a file is valid via the "Expires" header. So if you say "This file is valid for the next two days", the browser has no reason to contact your server.

max-age as given in your example does the same thing, basically.

  • I've included some headers. My goal is to have images cached until they need to be updated. I thought this was the purpose of etags. – Oliver Oct 27 '11 at 14:42
  • FRom http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html: "When the max-age cache-control directive is present in a cached response, the response is stale if its current age is greater than the age value given (in seconds) at the time of a new request for that resource." When a file is fresh, i.e. not stale, "a correct cache MUST respond". – Johan B.W. de Vries Oct 27 '11 at 14:55
  • Oh I see, the idea is to have no max-age or similar. but leave cache-control to public. – Oliver Oct 27 '11 at 15:05
  • Not sure; I think cache-control public still allows your browser to cache the file. I think what you want is must-revalidate. See also this quesion: http://stackoverflow.com/questions/6011123/must-revalidate-headers-of-this-request-wrong – Johan B.W. de Vries Oct 27 '11 at 15:29
  • cache-control public means that proxies in between the host and the browser can cache your data. cache-control private is for information that you don't want anyone in between holding onto. Anyway, it has nothing to do with cache invalidation. – skillet-thief Feb 13 '16 at 22:32
0

I solved this problem as follows:

Have a server side service that always redirects (with 302 Moved Temporarily) to the image you want to show, and on the user side have the image source be reset to the redirect + '?' + timestamp.

This forces the browser to query the image again but lets the cache work.

So e.g. your user side javascript requests /redirectme/avatar-xxx.jpg?121341 and it gets a 302 to /avatars/avatar-xxx.jpg which it then requests normally with Etag caching. You pay two roundtrips in return for not having to send the whole image every time.

Tested in Chrome 19 (doesn't need the timestamp query string) and Firefox 10 (does need the timestamp).

w00t
  • 17,944
  • 8
  • 54
  • 62