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:
- Is there an equivalent to
evaluatePreconditions()?
- Is there a better way of constructing of ETag strings?
Thanks for your thoughts!