Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ETag handling in Spring MVC REST

I am looking at switching from switching from Apache CXF RS with JAX RS to Spring MVC REST and see some problems with the way Spring MVC REST is currently handling ETags. Maybe I am not understanding right or there is a better way of achieving what is currently being done with JAX RS?

Using Apache CXF RS, the conditions for last modified timestamp and the ETag are evaluated inside the REST service (the condition evaluation is actually quite complicated, see RFC 2616 sections 14.24 and 14.26, so I am happy this is done for me). The code looks something like this:

@GET
@Path("...")
@Produces(MediaType.APPLICATION_JSON)
public Response findBy...(..., @Context Request request) {
       ... result = ...fetch-result-or-parts-of-it...;
       final EntityTag eTag = new EntityTag(computeETagValue(result), true);
       ResponseBuilder builder = request.evaluatePreconditions(lastModified, eTag);
       if (builder == null) {
              // a new response is required, because the ETag or time stamp do not match
              // ...potentially fetch full result object now, then:
              builder = Response.ok(result);
       } else {
              // a new response is not needed, send "not modified" status without a body
       }
       final CacheControl cacheControl = new CacheControl();
       cacheControl.setPrivate(true); // store in private browser cache of user only
       cacheControl.setMaxAge(15); // may stay unchecked in private browser cache for 15s, afterwards revalidation is required
       cacheControl.setNoTransform(true); // proxies must not transform the response
       return builder
              .cacheControl(cacheControl)
              .lastModified(lastModified)
              .tag(eTag)
              .build();
}

My attempt at the same thing with Spring MVC REST looks something like this:

@RequestMapping(value="...", produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<...> findByGpnPrefixCacheable(...) {
       ... result = ...fetch-result...;
//     ... result = ...fetch-result-or-parts-of-it...; - can't fetch parts, must obtain full result, see below
       final String eTag = "W/\""+computeETagValue(result)+"\""; // need to manually construct, as opposed to convenient JAX RS above
       return ResponseEntity
              .ok() // always say 'ok' (200)?
              .cacheControl(
                     CacheControl
                           .cachePrivate()
                           .maxAge(15, TimeUnit.SECONDS)
                           .noTransform()
                     )
              .eTag(eTag)
              .body(result); // ETag comparison takes place outside controller(?), but data for a full response has already been built - that is wasteful!
}

I take issue with having to build all data for a full response beforehand, even if not required. This makes the ETag as used in Spring MVC REST far less valuable than it could be. The idea of a weak ETag to my understanding is that it might be "cheap" to build its value and compare it. In the happy case, this prevents load on the server. In the case, the resource was modified the full response has to be built, of course.

It appears to me that by design Spring MVC REST currently requires the full response data to be built, no matter if a body for the response is ultimately needed or not.

So, in summary, two questions for Spring MVC REST:

  1. Is there an equivalent to evaluatePreconditions()?
  2. Is there a better way of constructing of ETag strings?

Thanks for your thoughts!

like image 690
JanDasWiesel Avatar asked Oct 21 '15 15:10

JanDasWiesel


1 Answers

  1. Yes, there is an equivalent method.

You can use it like this:

@RequestMapping
public ResponseEntity<...> findByGpnPrefixCacheable(WebRequest request) {

    // 1. application-specific calculations with full/partial data
    long lastModified = ...; 
    String etag = ...;

    if (request.checkNotModified(lastModified, etag)) {
        // 2. shortcut exit - no further processing necessary
        //  it will also convert the response to an 304 Not Modified
        //  with an empty body
        return null;
    }

    // 3. or otherwise further request processing, actually preparing content
    return ...;
}

Note that there are different versions of the checkNotModified method, with lastModified, ETag, or both.

You can find the documentation here: Support for ETag and Last-Modified response headers.


  1. Yes, but you have to change some things first.

You can change the way you calculate the ETag so you don't need to fetch the full result.

For example, if this fetch-result is a database query, you could add a version field to your entity and annotate it with @Version so that it is incremented each time it is modified, and then use that value for the ETag.

What does this accomplish? since you can configure the fetching to be lazy, you don't need to retrieve all the fields of your entities, and you also avoid having to hash it to build the ETag. Just use the version as the ETag string.

Here is a question on Using @Version in Spring Data project.

like image 116
ESala Avatar answered Oct 17 '22 02:10

ESala