10

I am trying to develop restful API using Jersey.I have GET API's for a particular get operation my GET is taking same time from the same client. Is it possible to cache response? Any pointers is appreciated.

Thanks

flash
  • 6,730
  • 7
  • 46
  • 70
chiru
  • 812
  • 5
  • 17
  • 32

4 Answers4

10

You can use CacheControl, eTag - follow below example code

// In your jersey method
    final EntityTag eTag = new EntityTag(resource.getId() + "_" +
     resource.getLastModified().getTime());
    final CacheControl cacheControl = new CacheControl();
    cacheControl.setMaxAge(-1);

    ResponseBuilder builder = request.evaluatePreconditions(
         resource.getLastModified(), eTag);

    // the resoruce's information was modified, return it
    if (builder == null) {
         builder = Response.ok(resource);
    }

    // the resource's information was not modified, return a 304

    return builder.cacheControl(cacheControl).lastModified(
         resource.getLastModified()).tag(eTag).build();

Replace resource with your Resource instance.

TheWhiteRabbit
  • 15,480
  • 4
  • 33
  • 57
  • Would u mind explaining why u disable Cache-Control (no-cache)? Its like telling: sry no caching possible but look here I have a useless Etag and Last-Modified headers. – Orri Nov 12 '13 at 17:50
  • 2
    it is to say, its cached forever but server can say whether resource is modified based on e-tag , hence client/browser can use it from cache – TheWhiteRabbit Nov 13 '13 at 09:18
8

Summary of solutions:

  1. Request as method parameter

    Interface:

     @Path("myentity")
     public interface MyEntityResource
    
         @GET
         @Produces(MediaType.APPLICATION_JSON)
         public Response getMyEntity(@Context final Request request);
     }
    

    Implementation:

     public class MyEntityResourceImpl implements MyEntityResource
    
         @Override
         public Response getMyEntity(final Request request) {
    
             final MyEntity myEntity = ... // load entity
             final String eTagValue = ... // calclutate value of ETag
    
             final EntityTag eTag = new EntityTag(eTagValue);
    
             ResponseBuilder responseBuilder = request.evaluatePreconditions(eTag);
    
             if (responseBuilder == null) {
                 return Response.ok(user).tag(eTag).build();
             }
    
             return responseBuilder.build();
         }
     }
    

    Disadvantages:

    • implementation detail Requestis exposed

    • return type Reponse is generic

    • missing grammar of return type in WADL

    • client proxy with unnecessary parameter Request

  2. Request as instance variable

    Interface:

     @Path("myentity")
     public interface MyEntityResource
    
         @GET
         @Produces(MediaType.APPLICATION_JSON)
         public Response getMyEntity();
     }
    

    Implementation:

     public class MyEntityResourceImpl implements MyEntityResource
    
         @Context
         private Request request
    
         @Override
         public Response getMyEntity() {
    
             final MyEntity myEntity = ... // load entity
             final String eTagValue = ... // calclutate value of ETag
    
             final EntityTag eTag = new EntityTag(eTagValue);
    
             ResponseBuilder responseBuilder = request.evaluatePreconditions(eTag);
    
             if (responseBuilder == null) {
                 return Response.ok(user).tag(eTag).build();
             }
    
             return responseBuilder.build();
         }
     }
    

    Disadvantages:

  3. ShallowEtagHeaderFilter as web filter

    web.xml:

     <filter>
         <filter-name>etagFilter</filter-name>
         <filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class>
     </filter>
     <filter-mapping>
         <filter-name>etagFilter</filter-name>
         <url-pattern>/api/*</url-pattern>
     </filter-mapping>
    

    Interface:

     @Path("myentity")
     public interface MyEntityResource
    
         @GET
         @Produces(MediaType.APPLICATION_JSON)
         public MyEntity getMyEntity();
     }
    

    Implementation:

     public class MyEntityResourceImpl implements MyEntityResource
    
         @Override
         public MyEntity getMyEntity() {
    
             final MyEntity myEntity = ... // load entity
             return myEntity;
         }
     }
    

    Disadvantages:

    • bad server performance, see JavaDoc

    • works only on uncommitted response

    • no support of weak ETag

  4. Custom WriterInterceptor as JAX-RS Interceptor

    Interceptor:

     public class CustomInterceptor implements WriterInterceptor {
    
         @Context
         private Request request;
    
         @Override
         public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
    
             OutputStream old = context.getOutputStream();
    
             ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    
             try {
    
                 context.setOutputStream(buffer);
                 context.proceed();
    
                 byte[] entity = buffer.toByteArray();
    
                 String etag = ... // calclutate value of ETag
                 context.getHeaders().putSingle(HttpHeaders.ETAG, etag);
    
                 ResponseBuilder responseBuilder = request.evaluatePreconditions(eTag);
    
                 if (responseBuilder == null) {
                      throw new WebApplicationException(responseBuilder.status(Response.Status.NOT_MODIFIED).header(HttpHeaders.ETAG, etag).build());
                 }
    
                 old.write(entity);
    
             } finally {
                 context.setOutputStream(old);
             }
         }
     }
    

    See also: ServerCacheInterceptor (Resteasy)

    Interface:

     @Path("myentity")
     public interface MyEntityResource
    
         @GET
         @Produces(MediaType.APPLICATION_JSON)
         public MyEntity getMyEntity();
     }
    

    Implementation:

     public class MyEntityResourceImpl implements MyEntityResource
    
         @Override
         public MyEntity getMyEntity() {
    
             final MyEntity myEntity = ... // load entity
             return myEntity;
         }
     }
    

    Disadvantages:

    • no predefined interceptor for Jersey available

    • bad server performance

    • no support of weak ETag

    • ugly workaround with WebApplicationException

Community
  • 1
  • 1
dur
  • 15,689
  • 25
  • 79
  • 125
3

You could use any caching mechanism applicable for standard java together with Jersey, like Ehcache.

You only have to pay attention to verify that your data in the backend hasn't changed.

Here is simple example with Ehcache:

@GET
@Path("{id}")
public List<Data> getData(@PathParam("id") Long id) {
    Element element = CacheManager.getInstance().getCache("test").get(id);
    if(element == null) {
        Data value = fetchElementFromBackend(id);
        CacheManager.getInstance().getCache("test").put(new Element(id, value));
        return value;
    }

    return element.getObjectValue();
}
flash
  • 6,730
  • 7
  • 46
  • 70
1

Recently I've been solving a similar (if not the same) problem. As a side result of it the following library emerged: https://github.com/AndreyLebedenko/dropwizard-caching-filter

The way it can be used is like below:

@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/cached")
@ResponseCachedByFilter(10000)
public Object getCached() {
    return dao.get();
} 

Hope it helps.

Andrey Lebedenko
  • 1,850
  • 17
  • 24