Often times I have seen API implementations not taking advantage of client side caching. Consider this example, a REST service needs to get data from a handful of other services and for every request, even though the upstream response might have not changed for the same input, it’s being calculated repeatedly and sent back to the client.
Depending on how expensive this calculation might be, wouldn’t be a better approach if the HTTP request includes data about what it previously has stored from a prior server response in an attempt for the server to find out if this calculation would be needed at all? This will improve the application performance while saving on server resources.
And what about if this expensive calculation is not needed, wouldn’t be a good practice for the server to let the client know that nothing has changed on the server side for that request? This will also save on bandwidth, assuming the client service is able to reconstruct the response payload.
This post focuses on the client side of this improvement, configuring Spring’s RestTemplate to use HttpClient and Ehcache to cache upstream HTTP responses using ETags.
This service includes a simple API returning a String. As part of the HTTP response, the ETag header value will be set to the md5 hash of the entity representation (the response body in this demo) via the Spring’s ShallowEtagHeaderFilter.
Basically this means the ETag header value will change for different String responses.
Let’s discuss the relevant parts of the Demo Service 2:
pom.xml:
spring-boot-starter-web dependency will be used to implement a RESTfulAPI using Spring.
Demo2CachingRestTemplateApplication.java:
This is the Spring Boot app’s start class as defined in *pom.xml*’s start-class property. It’s also taking care if registering the ShallowEtagHeaderFilter filter mentioned earlier. It’s worth mentioning that:
Important:ShallowEtagHeaderFilter implementation saves on bandwidth because if the If-None-Match header passed in the request matches the ETag header value to be included in the response, the body won’t be included but the HTTP status 304 - Not Modified.
It could be noticed that since the ETag’s md5 hash value is calculated for every request, it doesn’t save on server performance, which it’s OK to keep this how-to simple.
Tip: A more realistic example to demonstrate saving on server performance would be using ETags and conditional requests with JPA entities, @Version field and Second-Level cache.
Stay tuned and sign up to the newsletter to receive updates when content like this is published.
HelloResource.java:
A simple implementation of an endpoint to be used by Demo Service 1.
4. THE DEMO SERVICE 1
This service implements a simple API that uses RestTemplate to delegate requests to Demo Service 2 demonstrating how to configure it using HttpClient and Ehcache to cache responses using ETags. This approach saves us from explicitly caching, updating and evicting objects, managing TTLs, etc. with the associated overhead related to thread safety.
The relevant parts of the Demo Service 1 are:
pom.xml:
Similarly to Demo Service 2, spring-boot-starter-web dependency is included to implement an API using Spring MVC RESTful. spring-boot-starter-cache is a Spring Boot starter responsible for creating Caching-related beans depending on classes found in the classpath, for instance ehcache, the cache provider in this tutorial. httpclient library is used as the underlying library used by RestTemplate to send outbound requests and httpclient-cache is used to provide support for httpclient to cache responses.
packagecom.asimio.api.demo.main;...@SpringBootApplication(scanBasePackages={"com.asimio.api.demo.main","com.asimio.api.demo.rest"})@EnableCaching// for cacheManager and related beans to get auto-configuredpublicclassDemo1CachingRestTemplateApplication{@Value("#{cacheManager.getCache('httpClient')}")privateCachehttpClientCache;publicstaticvoidmain(String[]args){SpringApplication.run(Demo1CachingRestTemplateApplication.class,args);}@BeanpublicPoolingHttpClientConnectionManagerpoolingHttpClientConnectionManager(){PoolingHttpClientConnectionManagerresult=newPoolingHttpClientConnectionManager();result.setMaxTotal(20);returnresult;}@BeanpublicCacheConfigcacheConfig(){CacheConfigresult=CacheConfig.custom().setMaxCacheEntries(DEFAULT_MAX_CACHE_ENTRIES).build();returnresult;}@BeanpublicHttpCacheStoragehttpCacheStorage(){Ehcacheehcache=(Ehcache)this.httpClientCache.getNativeCache();HttpCacheStorageresult=newEhcacheHttpCacheStorage(ehcache);returnresult;}@BeanpublicHttpClienthttpClient(PoolingHttpClientConnectionManagerpoolingHttpClientConnectionManager,CacheConfigcacheConfig,HttpCacheStoragehttpCacheStorage){HttpClientresult=CachingHttpClientBuilder.create().setCacheConfig(cacheConfig).setHttpCacheStorage(httpCacheStorage).disableRedirectHandling().setConnectionManager(poolingHttpClientConnectionManager).build();returnresult;}@BeanpublicRestTemplaterestTemplate(HttpClienthttpClient){HttpComponentsClientHttpRequestFactoryrequestFactory=newHttpComponentsClientHttpRequestFactory();requestFactory.setHttpClient(httpClient);returnnewRestTemplate(requestFactory);}...
First the @EnableCaching allows the cacheManager to be auto-configured.
PoolingHttpClientConnectionManager, HttpClient and RestTemplate beans look similar to the ones included in Troubleshooting Spring’s RestTemplate Requests Timeout except that in this post the HttpClient object is instantiated using CachingHttpClientBuilder while in the other post the HttpClient bean was instantiated using HttpClientBuilder. \
Basically this three beans are used to configure the RestTemplate bean to use ApacheHttpClient instead of the default implementation which is based on the JDK plus some basic configuration such as the number of connections in the pool.
It’s also worth mentioning httpClient reference in line 10 refers to the cache name as found in ehcache.xml.
The other interesting beans are CacheConfig and HttpCacheStorage. HttpCacheStorage is in fact not required to provide client side caching. If it were removed (along with the @EnableCaching annotation), a BasicHttpCacheStorage or ManagedHttpCacheStorage implementation would be used instead as explained in the next table with configuration set in the CacheConfig bean.
HttpCacheStorage implementations
Description
BasicHttpCacheStorage
Default implementation if cacheDir is set when instantiating an HttpClient instance via CachingHttpClientBuilder.
EhcacheHttpCacheStorage
Discussed in this post, uses Ehcache as the backend.
ManagedHttpCacheStorage
Default implementation if cacheDir is not set when instantiating an HttpClient instance via CachingHttpClientBuilder.
MemcacheHttpCacheStorage
Uses Memcache as the backend.
But using EhcacheHttpCacheStorage allows for more configuration settings, the application might already be using Ehcache and its statistics, data and operations could be accessed via JMXMBean.
JMX - MBeans - Ehcache Stats
ehcache.xml:
The cache configuration used to store the HTTP responses.
HelloResource.java:
This is a sample API that sends requests to another web service where caching details are completely transparent to the application. This code doesn’t need to update the cache or evict items, etc.. In fact, it doesn’t know values might have been retrieved from a cache.
A successful 200 OK response, but let’s look at the logs generated when Demo Service 1 sends the request to Demo Service 2:
The interesting logs here are the 200 OK response from Demo Service 2 which also includes the header ETag: “023e8caa26fd7411445527af3d9aed055” and Hello orlando in the body. 023e8caa26fd7411445527af3d9aed055 being the md5 digest for Hello orlando.
First notice the request sent from Demo Service 1 now includes the header If-None-Match: “023e8caa26fd7411445527af3d9aed055”. Then look at Demo Service 2’s response status, 304 NOT MODIFIED with the same ETag value and no body. But the curl output was Hello orlando, that’s because it was retrieved from the cache.
And that’s it for this post.
Thanks for reading and as always, feedback is very much appreciated. If you found this post helpful and would like to receive updates when content like this gets published, sign up to the newsletter.
6. SOURCE CODE
Accompanying source code for this blog post can be found at:
Orlando L Otero is a Sr Software Engineer Consultant focusing on integration, modernization, cloud adoption and migration, microservices, API design and implementation, and agile delivery.