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:
evaluatePreconditions()?
Thanks for your thoughts!
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With