ETags and Browser Cache

Caching resources on the browser is one of the patterns followed to minimize redundant trips to the server and thus reduce load on the server. This works well for data that is static. But, there are cases where the data changes every once in a while (although less frequently) and you would want the browser to get the latest data without waiting for the cached data to expire. e.g. changes to a css or javascript in the new version of the application should be reflected immediately on the browser. To handle these refreshes, you might be tempted to make it a non-cacheable resource. Entity tags( or ETags for short) help you cache such resources.

ETag is a unique identifier that represents the response content of a request. By comparing Etags, you should be able to tell if the response for a request has changed or not, and decide if you need to send the new data back to the client or just use the cached data.

HTTP supports ETag response headers which can be used along with If-None-Match request headers for managing cache data. These headers are meant to be used for GET and HEAD requests, which are the recommended methods for fetching cacheable resources.

The following sequence diagrams illustrates the ETag workflow for an example of 3-step resource fetch. Each step requests for the same resource.





Step 1

  • Browser sends a request for a resource
  • Server creates an ETag value for the resource using hash or other techniques.
  • Server sets this value in "ETag" header in response.
  • Server sends the resource along with ETag.
  • Client caches the resource and the ETag

Step 2

  • Client requests for the same resource
  • Client sends the ETag value for the resource through the If-None-Match header.
  • Server calculates the ETag for the resource.
  • Server compares the new ETag with If-None-Match. 
  • If it matches, then server sends a 304 response code.
  • Client uses the data from cache.

Step 3

  • Client requests for the same resource.
  • Client sends the ETag value for the resource through the If-None-Match header.
  • Server calculates the ETag for the resource.
  • Server compares the new ETag with If-None-Match.
  • If it does not match, then server sets the ETag response header with the new value.
  • Server sends the resource along with ETag.
  • Client caches the new resource and the ETag


ETag generation


ETag generation in itself could add some amount of latency, depending upon the techniques used. The simplest technique to calculate is to generate a hash of the content through MD5 algorithm or something similar. This could be pretty sluggish depending upon the size of the content.
The ETag need not necessarily be unique, as long as it can indicate the change in the content. For example, in one of my projects I used the content version as the ETag. The version indicated change in the content and that was good enough for me to refresh the cache.

Spring support for ETags


ServletWebRequest


org.springframework.web.context.request.ServletWebRequest
provides utilities to compare ETags.

public boolean ServletWebRequest.checkNotModified(String etag)

This function compares the ETag and If-Not-Match headers and sets the response code or the ETag response header. You can get hold of the WebRequest instance through the Controller handlers.

Consider the following example for a better understanding.

@RequestMapping(value="mymodule/getMyResource.do", method = RequestMethod.GET)
@ResponseBody
public MyResource getMyResource(HttpServletRequest request, WebRequest webRequest)
{
   MyResource myResource = myService.getMyResource();
   String etag = generateHash(myResource);
   boolean notModified = webRequest.checkNotModified(eTag);
   if (! notModified) {
      //Forces revalidation of cache. Browser would send an If-Not-Match with ETag in the next request.
      response.setHeader("Cache-control", "max-age=0");
      return myResource;
   }
   else {
      return null;
   }
}

ShallowEtagHeaderFilter


In the above method, you would be responsible for generating the ETag. ShallowEtagHeaderFilter can be used to to both generate ETag and set the appropriate header values. It can be configured in the web.xml. It uses MD5 to generate the hash. Of course, you can always sub-class it to implement a custom ETag generation.
<filter>
   <filter-name>shallowEtagHeaderFilter</filter-name>
   <filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class>
</filter>
<filter-mapping>
   <filter-name>shallowEtagHeaderFilter</filter-name>
   <url-pattern>/mymodule/*</url-pattern>
</filter-mapping>
 

Last-Modified and If-Modified-Since


An alternate approach to ETags is using last modified time to check for data changes. These function behave exactly the same way as ETag and If-Not-Match.. Last-Modified is analogous to ETag and If-Modified-Since is to If-Not-Match. Again, it is useful if you can resolve the last modified time easily.

The following method in ServletWebRequest can be used for this.


public boolean ServletWebRequest.checkNotModified(long lastModifiedDate)


Final Disclaimer


ETags are based on response content, which means that you cannot avoid a trip to the server to compare the content. What you are saving however, is some bandwidth on the way back to client.