ETags для отдыха с весны
1. обзор
Эта статья будет посвященаworking with ETags in Spring, интеграционному тестированию REST API и сценариям потребления сcurl.
Дальнейшее чтение:
Введение в Spring REST Docs
В этой статье представлен Spring REST Docs, механизм, управляемый тестами, для создания документации для сервисов RESTful, которая является точной и удобочитаемой.
Пользовательский тип носителя для Spring REST API
Краткое введение в использование пользовательского типа мультимедиа в Spring REST API.
Нумерация страниц с помощью Spring REST и AngularJS таблицы
Подробно рассмотрим, как реализовать простой API с разбиением на страницы в Spring и как использовать его с AngularJS и UI Grid.
2. REST и ETags
Из официальной весенней документации по поддержке ETag:
ETag (тег объекта) - это заголовок ответа HTTP, возвращаемый веб-сервером, совместимым с HTTP / 1.1, который используется для определения изменения содержимого по заданному URL-адресу.
Мы можем использовать ETag для двух вещей - кеширования и условных запросов. ETag value can be thought of as a hash вычисляется из байтов тела ответа. Поскольку служба, скорее всего, использует криптографическую хеш-функцию, даже самая маленькая модификация тела резко изменит выходную информацию и, следовательно, значение ETag. Это верно только для сильных ETag - протокол также предоставляетweak Etag.
Using an If-* header turns a standard GET request into a conditional GET. Два заголовкаIf-*, которые используются с тегами ETag: «https://tools.ietf.org/html/rfc2616#section-14.26[If-None-Match]» и «https: / /tools.ietf.org/html/rfc2616#section-14.24[If-Match] »- каждый со своей собственной семантикой, как обсуждается далее в этой статье.
3. Связь клиент-сервер сcurl
Мы можем разбить простое соединение клиент-сервер, включающее ETag, на этапы:
Сначала Клиент выполняет вызов REST API -the Response includes the ETag header, который будет сохранен для дальнейшего использования:
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
For the next request, the Client will include the If-None-Match request header with the ETag value from the previous step. Если Ресурс не изменился на Сервере, Ответ не будет содержать тела и кода состоянияof 304 – Not Modified:
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 весной
Поддержка Spring: использование ETag в Spring чрезвычайно просто в настройке и полностью прозрачно для приложения. We can enable the support by adding a simple Filter вweb.xml:
etagFilter
org.springframework.web.filter.ShallowEtagHeaderFilter
etagFilter
/foos/*
Мы сопоставляем фильтр с тем же шаблоном URI, что и сам RESTful API. Сам фильтр является стандартной реализацией функциональности ETag начиная с Spring 3.0.
The implementation is a shallow one - приложение вычисляет ETag на основе ответа, что позволит сэкономить полосу пропускания, но не производительность сервера.
Таким образом, запрос, который получит пользу от поддержки ETag, будет по-прежнему обрабатываться как стандартный запрос, потреблять любой ресурс, который он обычно потребляет (соединения с базой данных и т. Д.), И только до того, как ответ будет возвращен клиенту, поддержка ETag будет отключена. в.
В этот момент ETag будет вычислен из тела ответа и установлен в самом ресурсе; кроме того, если в запросе был установлен заголовокIf-None-Match, он также будет обработан.
Более глубокая реализация механизма ETag потенциально может обеспечить гораздо большие преимущества - например, обслуживать некоторые запросы из кэша и вообще не выполнять вычисления - но реализация, безусловно, будет не такой простой и не такой подключаемой, как поверхностный подход. описано здесь.
4.1. Конфигурация на основе Java
Давайте посмотрим, как будет выглядеть конфигурация на основе Java наdeclaring a ShallowEtagHeaderFilter bean in our Spring context:
@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 ’sgetServletFilters .
4.2. Использование методаeTag() ResponseEntity
Этот метод был представлен в Spring framework 4.1 иwe can use it to control the ETag value that a single endpoint retrieves.
Например, представьте, что мы используем версионные сущности в качествеOptimist Locking mechanism для доступа к информации нашей базы данных.
Мы можем использовать саму версию в качестве 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-Not Modified, если условный заголовок запроса совпадает с данными кеширования.
5. Тестирование ETags
Начнем с простого -we need to verify that the response of a simple request retrieving a single Resource will actually return the “ETag” header:
@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"));
}
Next,we verify the happy path of the ETag behavior. Если в запросе на получениеResource с сервера используется правильное значение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);
}
Шаг за шагом:
-
мы создаем и получаем ресурс,storing значениеETag
-
отправить новый запрос на получение, на этот раз с заголовком «If-None-Match», определяющим ранее сохраненное значениеETag
-
по этому второму запросу сервер просто возвращает304 Not Modified, поскольку сам ресурс действительно не был изменен между двумя операциями поиска.
Наконец, мы проверяем случай, когда ресурс изменяется между первым и вторым запросами на извлечение:
@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);
}
Шаг за шагом:
-
сначала мы создаем и получаемResource - и сохраняем значениеETag для дальнейшего использования
-
затем обновляем тот жеResource
-
отправить новый запрос GET, на этот раз с заголовком «If-None-Match», указывающимETag, которые мы ранее сохранили
-
по этому второму запросу сервер вернет200 OK вместе с полным ресурсом, поскольку значениеETag больше неверно, поскольку мы тем временем обновили ресурс
Наконец, последний тест - который не сработает, потому что у функции естьnot yet been implemented in Spring - этоthe support for the If-Match HTTP header:
@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 Precondition Failed
6. ETags БОЛЬШИЕ
We have only used ETags for read operations.RFC exists пытается прояснить, как реализации должны поступать с ETags при операциях записи - это не стандартно, но представляет интерес.
Конечно, есть и другие возможные применения механизма ETag, например, для оптимистического механизма блокировки, а также для работы сrelated “Lost Update Problem”.
Есть также несколько известныхpotential pitfalls and caveats, о которых следует помнить при использовании ETags.
7. Заключение
Эта статья лишь поверхностно коснулась того, что возможно с Spring и ETags.
Для полной реализации службы RESTful с поддержкой ETag, наряду с интеграционными тестами, проверяющими поведение ETag, ознакомьтесь сGitHub project.