Java – Etag processing in spring MVC rest
I am considering switching from Apache CXF RS and Jax rs to spring MVC rest, and look at some problems in the way spring MVC rest is currently dealing with Etag Maybe I don't understand. Is there a better way to implement the work currently being done by Jax RS?
Use Apache CXF rs to evaluate the conditions of the last modified timestamp and Etag in the rest Service (the condition evaluation is actually quite complex, see RFC 2616 sections 14.24 and 14.26, so I'm happy to do it for me) The code looks 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(); }
I try the same thing as spring MVC rest. It looks 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! }
Even if not, I need to build all the data first to get a complete response This makes the Etag used in spring MVC rest far less valuable than it may be To my understanding, the idea of a weak Etag is to establish that its value may be "cheap" and compare it In happy situations, this prevents loading on the server When the resource is modified, of course, you need to build a complete response
In my opinion, by designing spring MVC rest, we need to build complete response data, regardless of whether the subject of the response is required in the end
Therefore, two questions about spring MVC rest are summarized:
>Is it equivalent to evaluateproconditions()? > Is there a better way to build Etag strings?
Thank you for your idea!
Solution
Yes, there is an equivalent method
You can use 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 ...; }
Please note that there are different versions of checknotmodified method, LastModified, Etag or both
You can find the document here: support for Etag and last modified response headers
Yes, but you have to change something first
You can change the way Etag is calculated so that you do not need to get complete results
For example, if the extraction result is a database query, you can add a version field to the entity and annotate it with @ version so that the field will be added each time you modify it, and then use the value for Etag
What's this for? Because you can configure fetching to be lazy, you don't need to retrieve all the fields of the entity, and you can also avoid hashing them to build etags Just use this version as the Etag string
This is a problem with using @ version in spring data project