Enterprise Java

HTTP Caching using JAX-RS

In the last blog we discussed different types of caches and their use cases.
In this post we will explore how we can leverage caching using HTTP response headers and the support provided by JAX-RS.

Expires Header

In HTTP 1.0, a simple response header called Expires would tell the browser how long it can cache an object or page. It would be a date in future after which the cache would not be valid. So, if we made an API call to retrieve data :
 
 

GET /users/1

The response header would be:

HTTP/1.1 200 OK
Content-Type: application/xml
Expires: Tue, 25 Aug 2013 16:00 GMT
-----
<user id="1">...</users>

This means the XML data is valid until 25th Aug 2013, 16:00 hours GMT.

JAX-RS supports this header in javax.ws.rs.core.Response object.

@Path("{id}")
	@GET
	@Produces(MediaType.APPLICATION_XML)
	public Response getUserXML(@PathParam("id") Long id){
		User user = userDB.get(id);
		ResponseBuilder builder = Response.ok(user,MediaType.APPLICATION_XML);
		//Putting expires header for HTTP broswer caching.
		Calendar cal = Calendar.getInstance();
		cal.set(2013,7,25,16,0);
		builder.expires(cal.getTime());
		return builder.build();
	}

But to support CDNs, proxy caches and revalidations there was a need for more enhanced headers with richer set of features, having more explicit controls. Hence in HTTP 1.1 few new headers were introduced and Expires was depricated. Lets explore them.

Cache-Control

Cache-Control has a variable set of comma-delimited directives that define who,how and for how long it can be cached. Lets explore few of them:

  • private/public : these are accessibility directives, private means a browser can cache the object but the proxies or CDNs can not and public makes it cachable by all.
  • -no-cache,no-store,max-age are few others where name tells the story.

JAX-RS provides javax.ws.rs.core.CacheControl class to represent this header.

@Path("{id}")
	@GET
	@Produces(MediaType.APPLICATION_XML)
	public Response getUserXMLwithCacheControl(@PathParam("id") Long id){
		User user = userDB.get(id);
		CacheControl cc = new CacheControl();
		cc.setMaxAge(300);
		cc.setNoStore(true);
		cc.setPrivate(true);
		ResponseBuilder builder = Response.ok(user,MediaType.APPLICATION_XML);
		builder.cacheControl(cc);
		return builder.build();
	}

Revalidation and Conditional GETs : After the cache has expired the cacher can revalidate the cache sending a request to the server to check if the cache is stale or holds good. This is done with the help of a header called as “Last-Modified“.

HTTP/1.1 200 OK
....
Cache-Control: max-age=1000
Last-Modified: Mon, 19 aug 2013 16:00 IST

To revalidate one must send a GET request with the header “If-modified-since“.This is called a conditional GET, in case the data is modified a response code 200 (OK) with current value of resource will be sent. And if the data is not modified a response code of “304″ is sent which would mean the cache is still valid, at this point the “Last-Modified” tag can be updated.

Etag

Etag is another HTTP header which can be used to revalidate caches, It is usually an MD5 hash value. A hash generated from resource is sent by server in the response as Etag value, so that while validating, client can send its Etag value to server to check if the value residing at the server matches.(As the hash is generated from resource, change in resource would generate a different hash)

For this conditional GET, a request with header “If-none-Match” is sent to validate.

GET /users/23 HTTP/1.1
If-None-Match: "23432423423454654667444"

Also, we can have strong and weak Etag values depending on different usecases.

JAX-RS provides us with javax.ws.rs.core.EntityTag for the same.

public class EntityTag {
.....
.....

To help with conditional GETs, JAX-RS also provided one injectable helper class Request, which has methods like…

....
ResponseBuilder evalutatePostConditions(EntityTag eTag);
ResponseBuilder evaluatePreConditions(Date isLastModified);
.....

The etag or LastModified values sent in the request Header are compared. Lets see an example…

@Path("{id}")
	@GET
	@Produces(MediaType.APPLICATION_XML)
	public Response getUserWithEtagSupport(@PathParam("id") Long id,
			@Context Request request){
		User user = userDB.get(id);
		//generating Etag out of hashCode of user
		EntityTag tag = new EntityTag(Integer.toString(user.hashCode()));
		CacheControl cc = new CacheControl();
		cc.setMaxAge(1000);

		ResponseBuilder builder = request.evaluatePreconditions(tag);
		if(builder!=null){
			//means the preconditions have been met and the cache is valid
			//we just need to reset the cachecontrol max age (optional)
			builder.cacheControl(cc);
			return builder.build();
		}

		//preconditions are not met and the cache is invalid
		//need to send new value with reponse code 200 (OK)
		builder = Response.ok(user,MediaType.APPLICATION_XML);
		//reset cache control and eTag (mandatory)
		builder.cacheControl(cc);
		builder.tag(tag);
		return builder.build();

	}

If the conditions have been met it returns a null which means that the latest tag and the tag provided in request header match, and there is no need to send new data with reponse OK. “304″ response meaning not-modified is send.

If the tags don’t match up, a new RequestBuilder object is returned in which we set the new etag and the current version of data (user in this case.)

This is how using JAX-RS we can leverage HTTP caching to its full potential effectively.
 

Reference: HTTP Caching using JAX-RS from our JCG partner Anirudh Bhatnagar at the anirudh bhatnagar blog.

Anirudh Bhatnagar

Anirudh is a Java programmer with extensive experience in building Java/J2EE applications. He has always been fascinated by the new technologies and emerging trends in software development. He has been involved in propagating these changes and new technologies in his projects. He is an avid blogger and agile enthusiast who believes in writing clean and well tested code.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
dan
dan
9 years ago

line 13 of the last snippet s/b
if(builder==null){

Someone
Someone
8 years ago

Shame on you for stealing the examples from Bill Burke’s book on JAXRS for Java.

Back to top button