There's more than one way to cache.
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-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.
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.
Web servers ignore query strings when serving a file from the file system. Caches, however, do not:
/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.
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.