As I mentioned recently on this blog, I'm working in my spare time on HTTP response cache implementation using Ehcache. When all said and done this will be a part of the Ehcache's web module. At this point the work on this is far from over but treat this as a status update.

Motivation

Who are the users of such an implementation? There was a feature request or two from the teams implementing REST clients to use Ehcache on the client side for optionally caching the GET operations based on various headers and directives.

High-level Design

This design leverages Java's Response cache mechanism, introduced back in version 1.5. I've written about it in an earlier post. On a GET request, Java's URLConnection's getInputStream() method invokes appropriate  protocol handler, which in turn interacts with the concrete implementation of the java.net.ResponseCache.

Java's mechanism does not provide any default cache implementation, but you can extend java.net.ResponseCache and write your own. EhcacheResponseCacheAdaptor extends ResponseCache and implements its get() and put() methods. Also, when an instance of EhcacheResponseCacheAdaptor is created it registers itself with the JVM as a default cache.

Whenever the system tries to load a URL (using URLConnection) via a protocol handler it first checks in the cache via get(). If the data is in the cache, it is returned if not a connection is made to the origin server to GET the content. After downloading the content the protocol handler then attempts to put it in the cache via put().

URI Mappings and Cache

The caller after creating a new instance of EhcacheResponseCacheAdaptor calls its addMapping() method to register a specific URI pattern to a cache that is defined in the ehcache config file. For example:

EhcacheResponseCacheAdaptor responseCache =
new EhcacheResponseCacheAdaptor(new CacheManager(););
responseCache.addMapping("http://somedomain.com:9000/rest/customers/",
"sampleCache1");

An instance of Ehcache's CacheManager is passed to the constructor of the adaptor. And then one or more URIs can be mapped to the caches.

Cache elements

If you used or using Ehcache you know that Element needs a key and its value. Adaptor implementation parses the URI, matches that to a cache and the rest of the URI is treated as a key. So if a request is made for http://somedomain.com:9000/rest/customers/12887876320, system identifies that the user intends to use sampleCache1 as the cache, and the remaining part of the URI that is not a part of the mapping is used as the key. In this example 12887876320 is used as the key.

Cacheability

Cacheability determination is made based based on Cache-control (no-cache, max-age=0) and/or Pragma headers, supporting both HTTP 1.0 and 1.1.

Invalidation

Time-to-live (TTL) calculation is made using Cache-control (max-age, min-fresh) and/or Expires headers.  TTL is assigned to the element directly. If the required headers are not provided it uses the values provided from the cache configuration.

Conditional GET

One of the important directives of conditional GET is only-if-cached (of Cache-control header). With this directive the client indicates to the system to fetch the value only from the cache if present, and not to request from the origin server. I've looked briefly into the protocol handler implementation and that seemed to be handling this scenario and perhaps I don't have to do much here.

From the User's point of view

For the end-user once they create the adaptor and add the mappings (as described above) not much is there to be done. They would perform the actions without any specific knowledge of this ResponseCache implementation. Following code would do just fine ..


URL url = new URL("http://somedomain.com:9000/rest/customers/12887876320");

URLConnection urlConnection = url.openConnection();
urlConnection.setDefaultUseCaches(true);
InputStream inputStream = urlConnection.getInputStream();

...

Next Steps

There are still quite a few loose ends in the implementation which need some tightening. The challenge is to provide support for as many HTTP headers from RFC-2616 as reasonable, Etag is one of them that I still have to take a look. More soon ...