Обработка ETag в Spring MVC REST

Я рассматриваю переход от переключения с Apache CXF RS с JAX RS на Spring MVC REST и некоторые проблемы с тем, как Spring MVC REST в настоящее время обрабатывает ETags. Может быть, я не понимаю права или есть лучший способ достичь того, что в настоящее время делается с JAX RS?

Используя Apache CXF RS, условия для последней измененной метки времени и ETag оцениваются внутри службы REST (оценка состояния на самом деле довольно сложная, см. Разделы 14.24 и 14.26 RFC 2616, поэтому я счастлив, что это сделано для меня). Код выглядит примерно так:

@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();
}

Моя попытка сделать то же самое с Spring MVC REST выглядит примерно так:

@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!
}

Я принимаю решение о том, что нужно заранее собрать все данные для полного ответа, даже если это не требуется. Это делает ETag используемым в Spring MVC REST гораздо менее ценным, чем это могло бы быть. Идея слабого ETag для моего понимания заключается в том, что это может быть "дешево", чтобы построить свою ценность и сравнить ее. В счастливом случае это предотвращает загрузку на сервер. В этом случае ресурс был изменен, конечно, полный ответ должен быть построен.

Мне кажется, что по дизайну Spring MVC REST в настоящее время требует, чтобы все данные ответа были построены, независимо от того, требуется ли в конечном итоге тело для ответа.

Итак, в общем, два вопроса для Spring MVC REST:

  1. Существует ли эквивалент для evaluatePreconditions()?
  2. Есть ли лучший способ построения строк ETag?

Спасибо за ваши мысли!

Ответ 1

  • Да, существует эквивалентный метод.

Вы можете использовать его следующим образом:

@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 ...;
}

Обратите внимание, что существуют разные версии метода checkNotModified с lastModified, ETag или обоими.

Документацию можно найти здесь: Поддержка заголовков ответов ETag и Last-Modified.


  1. Да, но сначала вам нужно изменить некоторые вещи.

Вы можете изменить способ вычисления ETag, поэтому вам не нужно получать полный результат.

Например, если этот результат выборки является запросом базы данных, вы можете добавить в свою версию поле версии и аннотировать его с помощью @Version, чтобы он увеличивался каждый раз, когда он был изменен, а затем используйте это значение для ETag.

Что это делает? поскольку вы можете настроить выборку как ленив, вам не нужно извлекать все поля ваших объектов, и вы также избегаете хэш-функции для создания ETag. Просто используйте версию как строку ETag.

Вот вопрос о Использование @Version в проекте Spring Data.