There's more than one way to cache.
Conditional GET
If you are storing these images on the file system and serving them directly through the web server, you are probably already using conditional get. The web server will automatically use filesystem metadata to set an ETAG header, and will automatically reply with "304 Not Modified" if the browser includes If-Modified-Since
or If-Matches
headers in its request. (All browsers will.)
In this case the whole image is not served back, so you have bandwidth savings. However, a GET request will still be issued, so you will still have the overhead and latency of a request.
You can decrease the number of requests slightly at the expense of cache freshness by having your webserver set Cache-Control
headers with a public,max-age=N
value for your images. This says that caches can keep the resource for at most max-age
seconds before they have to check if it is updated.
However, HTTP defines only one way to invalidate a cache entry, which may not fit your application's semantics: if you POST or PUT to a url that updates the profile photo, reply with a Location: [url of photo]
header and the cache entry for that url will be invalidated.
(This is the mechanism that allows you to cache a webpage with comments, and then have the page forcibly reloaded by the browser after the user posts a new comment. The browser would reply to a POST /comment
with 303 See Other
and a Location: /page/with/comment
. Note that this didn't used to work in Firefox due to a longstanding bug.)
Unless you have a lot of traffic, this approach to caching is fine.
Changing urls
A url is a representation of a resource, so another way to manage caching is not to change cache parameters for the resource, but to make a brand new resource with an "cache forever" directive. This is the approach that the "big boys" favor, because it allows them to generate no extra requests, saving them lots of bandwidth. The downside is it requires much more additional bookkeeping.
There are two general techniques for this.
Query strings
Web servers ignore query strings when serving a file from the file system. Caches, however, do not: /1.jpg?t=12345
and /1.jpg?t=67890
are two completely different, unrelated resources, even though the server thinks they are the same.
So one easy thing you can do is append the filesystem timestamp as a query string whenever you make a reference to a resource in your html, and set a long Expires
header. The browser will then cache this resource forever and not do any GETs as long as the query string does not change.
A downside is that it's difficult or impossible to instruct the webserver of the new url for an item if you want to forcibly invalidate a cache. For example, if a browser has a cached HTML page with a /1.jpg?v=1
reference, but happened to clear the entry for /1.jpg?v=1
(maybe it ran out of file or memory space), it will make a new request to /1.jpg?v=1
. If in the meantime the image has changed to /1.jpg?v=2
, the proper response is either:
- Serve the old version of the file. You would do this if you wanted all resources to be consistent with one another as they were at a certain point in time. This is what you should do with CSS files, for example, since a new css file with an old html file may not work properly!
- Redirect to the new version of the file using
301 Moved Permanently
. You would do this if you wanted all resources to be as new as possible.
Both of these are difficult to do with a webserver alone, which means you need to invoke a web application even for image requests, which can be both more complicated and more resource-intensive. Webservers are very fast at serving files, so the overhead of a web application may end up swallowing your bandwidth and latency gains.
File names
Instead of adding a query string, you change the filename. This means it's easy to keep multiple versions of files on the file system, but you will probably need to store file metadata and do other database book-keeping to keep track of your resources and their names.