ETag یا Entity Tag یک هدر است که از Http ورژن 1.1 اضافه شده است در این بخش به کاربرد و نحوه استفاده از آن در REST میپردازیم
استفاده از ETag دو کاربرد دارد :
- caching
- conditional request
مقدار ETag را معمولا معادل هش کد بایت های محتوا در نظر میگیرند و اگر محتوا بروز شده باشد مقدار هش کد نیز تغییر خواهد کرد
هدر *-if :
استفاده از هدر های *-if دارند بصورت استاندارد شرطی GET در می آیند دو نمونه از آن “If-None-Match” و “If-Match” هستند که در ادامه به آنها میپردازیم
ارتباط بین Client و Server با curl :
ما میتوانیم یک ارتباط ساده بین client و server را با کمک curl ایجاد کنیم ابتدا کلاینت با کمک curl یک ارتباط به سرور برقرار میکند و در جواب دریافت شده از سرور هدر 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 به سرور اعلام میکند که محتوای مورد نظر را ذخیره دارد و در صورتی که متحوا دارای تغییری بود آنرا ارسال کند و اگر نبود کلاینت را مطلع کند که چون قبلا آنرا دریافت کرده بود و سمت سرور هم تغییری نداشته کد وضعیت 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
و دوباره درخواست را ارسال میکنیم :
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
استفاده از ETag در Spring :
کافی است آنرا با اضافه کردن یک فیلتر در web.xml فعال کنیم :
<filter> <filter-name>etagFilter</filter-name> <filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class> </filter> <filter-mapping> <filter-name>etagFilter</filter-name> <url-pattern>/foos/*</url-pattern> </filter-mapping>
* آدرس فیلتر را روی آدرس سرویس RESTful ست کردیم و ویژگی ETag در این آدرس فعال است
ست کردن تنظیمات بوسیله کد java :
ابتدا یک Bean از نوع ShallowEtagHeaderFilter ایجاد میکنیم :
@Bean public ShallowEtagHeaderFilter shallowEtagHeaderFilter() { return new ShallowEtagHeaderFilter(); }
چنانچه نیاز به تنظیمات گسترده تری داریم میتوانیم از FilterRegistrationBean یک Bean بسازیم :
@Bean public FilterRegistrationBean<ShallowEtagHeaderFilter> shallowEtagHeaderFilter() { FilterRegistrationBean<ShallowEtagHeaderFilter> filterRegistrationBean = new FilterRegistrationBean<>( new ShallowEtagHeaderFilter()); filterRegistrationBean.addUrlPatterns("/foos/*"); filterRegistrationBean.setName("etagFilter"); return filterRegistrationBean; }
اگر از Spring Boot استفاده نمیکنید برای ست کردن تنظیمات میبایست از کلاس AbstractAnnotationConfigDispatcherServletInitializer متد getServletFilters استفاده کنیم
استفاده از متد ()eTag در ResponseEntity :
این متد در Spring 4.1 معرفی شد و با استفاده از آن میتوان روی مقدار ETag کنترل داشته باشیم
@GetMapping(value = "/{id}/custom-etag") public ResponseEntity<Foo> findByIdWithCustomEtag(@PathVariable("id") final Long id) { // ...Foo foo = ... return ResponseEntity.ok() .eTag(Long.toString(foo.getVersion())) .body(foo); }
تست :
ابتدا باید تایید کنیم که Response دریافتی شامل هدر 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")); }
سپس تست میکنیم که اگر محتوا تغییر کرده بود سرور آنرا ارسال کند وگرنه کد وضعیت 304 را ارسال کند و از انجا که محتوا تغییری نداشته 304 بودنش چک میشود :
@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); }
حال محتوا را تغییر میدهیم و دوباره تست میکنیم اینبار باید محتوا تغییر یافته ارسال شود
@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 هد If-Match استفاده شده است :
@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 با یک مقدار تصادفی که در سرور سابقه ای ندارد ارسال شده و در پاسخ کد 412 (Precondition Failed) یا شکست پیش شرط ارسال شده است
کاربرد ETag وسیع تر از چیزی است که ارائه شد برای اطلاعات بیشتر میتوان به RFC آن مراجعه کرد
دوتا از کاربرد های جالب آن در مسئله های
- Optimistic Locking Mechanism مکانیزم قفل بهینه
- Lost Update Problem مشکل بروزرسانی گمشده
است
همچنین باید مواظب مشکلاتی که استفاده از ETag میتواند ایجاد کند باشیم