Рубрики
Без рубрики

ETags для ОТДЫХА с весной

ETags с Spring – ShallowEtagHeaderFilter, интеграционное тестирование REST API и сценарии потребления с помощью curl.

Автор оригинала: Eugen Paraschiv.

1. Обзор

Эта статья будет посвящена работе с ETags весной , интеграционному тестированию REST API и сценариям потребления с curl .

Дальнейшее чтение:

Введение в документы весеннего ОТДЫХА

Пользовательский тип носителя для API Spring REST

Разбиение на страницы с весенним отдыхом и таблицей AngularJS

2. ОТДЫХ и ETags

Из официальной весенней документации по поддержке ETag:

Мы можем использовать ETags для двух вещей – кэширования и условных запросов. Значение ETag можно рассматривать как хэш , вычисленный из байтов тела ответа. Поскольку служба, скорее всего, использует криптографическую хэш-функцию, даже самая незначительная модификация тела резко изменит выходные данные и, следовательно, значение ETag. Это верно только для сильных ETAG – протокол также предоставляет слабый Etag .

Использование заголовка If-* превращает стандартный запрос GET в условный GET. Два заголовка If -* , которые используются с ETags,-это ” If-None-Match ” и ” If-Match ” – каждый со своей собственной семантикой, как описано далее в этой статье.

3. Связь Клиент-Сервер С curl

Мы можем разбить простое взаимодействие между клиентом и сервером, включающее ETAG, на следующие этапы:

Во – первых, Клиент делает вызов REST API – Ответ включает заголовок ETag , который будет сохранен для дальнейшего использования:

curl -H "Accept: application/json" -i http://localhost:8080/spring-boot-rest/foos/1
HTTP/1.1 200 OK
ETag: "f88dd058fe004909615a64f01be66a7"
Content-Type: application/json;charset=UTF-8
Content-Length: 52

Для следующего запроса Клиент будет включать заголовок If-None-Match запроса со значением ETag из предыдущего шага. Если Ресурс не изменился на Сервере, ответ не будет содержать тела и кода состояния 304 – Не изменен :

curl -H "Accept: application/json" -H 'If-None-Match: "f88dd058fe004909615a64f01be66a7"'
 -i http://localhost:8080/spring-boot-rest/foos/1
HTTP/1.1 304 Not Modified
ETag: "f88dd058fe004909615a64f01be66a7"

Теперь, прежде чем снова извлекать ресурс, давайте изменим его, выполнив обновление:

curl -H "Content-Type: application/json" -i 
  -X PUT --data '{ "id":1, "name":"Transformers2"}' 
    http://localhost:8080/spring-boot-rest/foos/1
HTTP/1.1 200 OK
ETag: "d41d8cd98f00b204e9800998ecf8427e" 
Content-Length: 0

Наконец, мы отправляем последний запрос, чтобы снова получить Foo. Имейте в виду, что мы обновили его с момента последнего запроса, поэтому предыдущее значение ETag больше не должно работать. Ответ будет содержать новые данные и новый ETag, который, опять же, может быть сохранен для дальнейшего использования:

curl -H "Accept: application/json" -H 'If-None-Match: "f88dd058fe004909615a64f01be66a7"' -i 
  http://localhost:8080/spring-boot-rest/foos/1
HTTP/1.1 200 OK
ETag: "03cb37ca667706c68c0aad4cb04c3a211"
Content-Type: application/json;charset=UTF-8
Content-Length: 56

И вот у вас есть это – ETags в дикой природе и экономия пропускной способности.

4. Поддержка ETag весной

Переход К поддержке пружины: использование ETag в Spring чрезвычайно легко настроить и полностью прозрачно для приложения. Мы можем включить поддержку, добавив простой Фильтр в web.xml :


   etagFilter
   org.springframework.web.filter.ShallowEtagHeaderFilter


   etagFilter
   /foos/*

Мы сопоставляем фильтр с тем же шаблоном URI, что и сам API RESTful. Сам фильтр является стандартной реализацией функциональности ETag с весны 3.0.

Реализация является неглубокой – приложение вычисляет ETag на основе ответа, что позволит сэкономить пропускную способность, но не производительность сервера.

Таким образом, запрос, который выиграет от поддержки ETag, по-прежнему будет обрабатываться как стандартный запрос, потреблять любой ресурс, который он обычно потребляет (подключения к базе данных и т. Д.), И только до того, как его ответ будет возвращен клиенту, начнет работать поддержка ETag.

В этот момент ETag будет вычислен из тела ответа и установлен на самом ресурсе; кроме того, если в запросе был установлен заголовок If-None-Match , он также будет обработан.

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

4.1. Конфигурация на основе Java

Давайте посмотрим, как будет выглядеть конфигурация на основе Java, объявив ShallowEtagHeaderFilter bean в нашем весеннем контексте :

@Bean
public ShallowEtagHeaderFilter shallowEtagHeaderFilter() {
    return new ShallowEtagHeaderFilter();
}

Имейте в виду, что если нам нужно предоставить дополнительные конфигурации фильтров, мы можем вместо этого объявить экземпляр FilterRegistrationBean :

@Bean
public FilterRegistrationBean shallowEtagHeaderFilter() {
    FilterRegistrationBean filterRegistrationBean
      = new FilterRegistrationBean<>( new ShallowEtagHeaderFilter());
    filterRegistrationBean.addUrlPatterns("/foos/*");
    filterRegistrationBean.setName("etagFilter");
    return filterRegistrationBean;
}

Наконец, если мы не используем Spring Boot, мы можем настроить фильтр с помощью метода AbstractAnnotationConfigDispatcherServletInitializer ‘s getServletFilters .

4.2. Использование метода ETag() Объекта ответа

Этот метод был введен в Spring framework 4.1, и мы можем использовать его для управления значением ETag, которое получает одна конечная точка .

Например, представьте, что мы используем версионные сущности в качестве механизма блокировки оптимистов для доступа к информации нашей базы данных.

Мы можем использовать саму версию в качестве ETag, чтобы указать, была ли изменена сущность:

@GetMapping(value = "/{id}/custom-etag")
public ResponseEntity
  findByIdWithCustomEtag(@PathVariable("id") final Long id) {

    // ...Foo foo = ...

    return ResponseEntity.ok()
      .eTag(Long.toString(foo.getVersion()))
      .body(foo);
}

Служба получит соответствующее состояние 304-Не изменено , если условный заголовок запроса соответствует данным кэширования.

5. Тестирование ETags

Давайте начнем с простого – нам нужно убедиться, что ответ на простой запрос, извлекающий один ресурс, действительно вернет заголовок “ ETag” :

@Test
public void givenResourceExists_whenRetrievingResource_thenEtagIsAlsoReturned() {
    // Given
    String uriOfResource = createAsUri();

    // When
    Response findOneResponse = RestAssured.given().
      header("Accept", "application/json").get(uriOfResource);

    // Then
    assertNotNull(findOneResponse.getHeader("ETag"));
}

Далее , мы проверяем счастливый путь поведения ETag. Если запрос на извлечение Ресурса с сервера использует правильное значение ETag , то сервер не извлекает ресурс:

@Test
public void givenResourceWasRetrieved_whenRetrievingAgainWithEtag_thenNotModifiedReturned() {
    // Given
    String uriOfResource = createAsUri();
    Response findOneResponse = RestAssured.given().
      header("Accept", "application/json").get(uriOfResource);
    String etagValue = findOneResponse.getHeader(HttpHeaders.ETAG);

    // When
    Response secondFindOneResponse= RestAssured.given().
      header("Accept", "application/json").headers("If-None-Match", etagValue)
      .get(uriOfResource);

    // Then
    assertTrue(secondFindOneResponse.getStatusCode() == 304);
}

Шаг за шагом:

  • мы создаем и извлекаем ресурс, сохраняя значение ETag
  • отправьте новый запрос на извлечение, на этот раз с заголовком ” If-None-Match “, указывающим ранее сохраненное значение ETag
  • при этом втором запросе сервер просто возвращает 304 Не измененный , поскольку сам ресурс действительно не был изменен между двумя операциями извлечения

Наконец, мы проверяем случай, когда ресурс изменяется между первым и вторым запросами на извлечение:

@Test
public void 
  givenResourceWasRetrievedThenModified_whenRetrievingAgainWithEtag_thenResourceIsReturned() {
    // Given
    String uriOfResource = createAsUri();
    Response findOneResponse = RestAssured.given().
      header("Accept", "application/json").get(uriOfResource);
    String etagValue = findOneResponse.getHeader(HttpHeaders.ETAG);

    existingResource.setName(randomAlphabetic(6));
    update(existingResource);

    // When
    Response secondFindOneResponse= RestAssured.given().
      header("Accept", "application/json").headers("If-None-Match", etagValue)
      .get(uriOfResource);

    // Then
    assertTrue(secondFindOneResponse.getStatusCode() == 200);
}

Шаг за шагом:

  • сначала мы создаем и извлекаем Ресурс – и сохраняем значение ETag для дальнейшего использования
  • когда мы обновляем один и тот же ресурс
  • отправьте новый запрос GET, на этот раз с заголовком ” If-None-Match “, указывающим ETag , который мы ранее сохранили
  • при этом втором запросе сервер вернет 200 OK вместе с полным ресурсом, так как значение ETag больше не является правильным, поскольку мы тем временем обновили ресурс

Наконец, последний тест – который не будет работать, потому что функциональность еще не была реализована весной – это поддержка заголовка If-Match HTTP:

@Test
public void givenResourceExists_whenRetrievedWithIfMatchIncorrectEtag_then412IsReceived() {
    // Given
    T existingResource = getApi().create(createNewEntity());

    // When
    String uriOfResource = baseUri + "/" + existingResource.getId();
    Response findOneResponse = RestAssured.given().header("Accept", "application/json").
      headers("If-Match", randomAlphabetic(8)).get(uriOfResource);

    // Then
    assertTrue(findOneResponse.getStatusCode() == 412);
}

Шаг за шагом:

  • мы создаем ресурс
  • затем извлеките его с помощью заголовка ” If-Match “, указывающего неправильное значение ETag – это условный запрос GET
  • сервер должен вернуть 412 Предварительное условие Не выполнено

6. ETags Большие

Мы использовали теги только для операций чтения. An RFC существует попытка прояснить, как реализации должны иметь дело с ETAGS при операциях записи – это не стандартно, но интересно читать.

Конечно, существуют и другие возможные варианты использования механизма ETag, например, для оптимистичного механизма блокировки, а также для решения связанной с “Проблемой потерянного обновления” .

Существует также несколько известных потенциальных подводных камней и предостережений , о которых следует помнить при использовании ETags.

7. Заключение

Эта статья только поцарапала поверхность тем, что возможно с пружиной и ETags.

Для полной реализации службы RESTful с поддержкой ETag, а также интеграционных тестов, проверяющих поведение ETag, ознакомьтесь с проектом GitHub .