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

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

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

@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. Есть ли эквивалент для оценки предусловий ()?
  2. Есть ли лучший способ построения строк ETag?

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

1 ответ

Решение
  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.

Другие вопросы по тегам