ETag untuk REST dengan Spring

REST Teratas

Saya baru sahaja mengumumkan kursus Learn Spring yang baru , yang berfokus pada asas-asas Spring 5 dan Spring Boot 2:

>> SEMAK KURSUS

1. Gambaran keseluruhan

Artikel ini akan memberi tumpuan kepada bekerja dengan ETag pada musim bunga , pengujian integrasi REST API dan senario penggunaan dengan curl .

2. REST dan ETag

Dari dokumentasi Spring rasmi mengenai sokongan ETag:

ETag (tag entiti) adalah tajuk respons HTTP yang dikembalikan oleh pelayan web yang mematuhi HTTP / 1.1 yang digunakan untuk menentukan perubahan kandungan pada URL tertentu.

Kami boleh menggunakan ETag untuk dua perkara - permintaan cache dan bersyarat. Yang nilai ETag boleh dianggap sebagai hash yang dikira daripada bait badan Respon. Oleh kerana perkhidmatan tersebut mungkin menggunakan fungsi hash kriptografi, pengubahsuaian terkecil dari badan akan mengubah output secara drastik dan dengan demikian nilai ETag. Ini hanya berlaku untuk ETag yang kuat - protokol juga menyediakan Etag yang lemah.

Menggunakan header If- * mengubah permintaan GET standard menjadi GET bersyarat. Dua header If- * yang menggunakan ETag adalah "If-None-Match" dan "If-Match" - masing-masing dengan semantiknya sendiri seperti yang dibahas kemudian dalam artikel ini.

3. Komunikasi Pelanggan-Pelayan Dengan keriting

Kami dapat menguraikan komunikasi Pelanggan-Pelayan yang mudah yang melibatkan ETag ke beberapa langkah:

Pertama, Pelanggan membuat panggilan API REST - Respons merangkumi tajuk ETag yang akan disimpan untuk penggunaan selanjutnya:

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

Untuk permintaan seterusnya, Pelanggan akan memasukkan tajuk permintaan If-None-Match dengan nilai ETag dari langkah sebelumnya. Sekiranya Sumber tidak berubah pada Pelayan, Respons tidak akan mengandungi isi badan dan kod status 304 - Tidak Diubah :

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

Sekarang, sebelum mengambil Sumber semula, mari ubah dengan melakukan kemas kini:

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

Akhirnya, kami menghantar permintaan terakhir untuk mendapatkan semula Foo. Perlu diingat bahawa kami telah mengemas kini sejak kali terakhir kami memintanya, jadi nilai ETag sebelumnya tidak lagi berfungsi. Respons tersebut akan mengandungi data baru dan ETag baru yang sekali lagi dapat disimpan untuk penggunaan selanjutnya:

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

Dan di sana anda memilikinya - ETag secara liar dan menjimatkan lebar jalur.

4. Sokongan ETag pada Musim Bunga

Menghidupkan sokongan: menggunakan ETag di Spring sangat mudah disediakan dan telus sepenuhnya untuk aplikasi. Kami boleh mengaktifkan sokongan dengan menambahkan Filter sederhana di web.xml :

 etagFilter org.springframework.web.filter.ShallowEtagHeaderFilter   etagFilter /foos/* 

Kami memetakan penapis pada corak URI yang sama dengan RESTful API itu sendiri. Penapis itu sendiri adalah pelaksanaan standard fungsi ETag sejak Spring 3.0.

Pelaksanaannya dangkal - aplikasi mengira ETag berdasarkan tindak balas, yang akan menjimatkan lebar jalur tetapi bukan prestasi pelayan.

Oleh itu, permintaan yang akan mendapat manfaat daripada sokongan ETag akan tetap diproses sebagai permintaan standard, menggunakan sumber yang biasa digunakan (sambungan pangkalan data, dan lain-lain) dan hanya sebelum responsnya dikembalikan kepada pelanggan, ETag akan menyokong dalam.

Pada ketika itu ETag akan dikira keluar dari badan Respons dan ditetapkan pada Sumber itu sendiri; juga, jika tajuk If-None-Match ditetapkan pada Permintaan, ia juga akan ditangani.

Pelaksanaan mekanisme ETag yang lebih mendalam berpotensi memberikan manfaat yang jauh lebih besar - seperti melayani beberapa permintaan dari cache dan tidak harus melakukan perhitungan sama sekali - tetapi pelaksanaannya pasti tidak sesederhana, atau tidak dapat diterapkan seperti pendekatan dangkal diterangkan di sini.

4.1. Konfigurasi Berasaskan Java

Mari kita lihat bagaimana konfigurasi berasaskan Java seperti dengan menyatakan kacang ShallowEtagHeaderFilter dalam konteks Spring kami :

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

Perlu diingat bahawa jika kita perlu memberikan konfigurasi penapis lebih lanjut, kita sebaliknya dapat menyatakan contoh FilterRegistrationBean :

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

Akhir sekali, jika kita tidak menggunakan Spring Boot kita boleh menetapkan penapis menggunakan AbstractAnnotationConfigDispatcherServletInitializer 's getServletFilters kaedah.

4.2. Menggunakan Kaedah eTag ResponseEntity ()

Kaedah ini diperkenalkan dalam rangka kerja Spring 4.1, dan kita dapat menggunakannya untuk mengawal nilai ETag yang diambil oleh satu titik akhir .

Sebagai contoh, bayangkan kita menggunakan entiti versi sebagai mekanisme Penguncian Optimis untuk mengakses maklumat pangkalan data kami.

Kita boleh menggunakan versi itu sendiri sebagai ETag untuk menunjukkan sama ada entiti telah diubah:

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

Perkhidmatan akan mendapatkan keadaan 304-Tidak diubah suai yang sesuai jika tajuk bersyarat permintaan itu sepadan dengan data cache.

5. Menguji ETag

Mari kita mulakan - kita perlu mengesahkan bahawa respons permintaan mudah yang mengambil satu Sumber akan mengembalikan tajuk "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")); }

Seterusnya , kami mengesahkan jalan senang tingkah laku ETag. Sekiranya Permintaan untuk mendapatkan Sumber dari pelayan menggunakan nilai ETag yang betul , maka pelayan tidak mengambil Sumber:

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

Langkah demi langkah:

  • kami membuat dan mengambil semula Sumber, menyimpan yang ETag nilai
  • kirim permintaan pengambilan baru, kali ini dengan tajuk " If-None-Match " yang menentukan nilai ETag yang disimpan sebelumnya
  • atas permintaan kedua ini, pelayan hanya mengembalikan 304 Tidak Diubahsuai , kerana Sumber itu sendiri memang tidak diubah antara kedua operasi pengambilan

Akhirnya, kami mengesahkan kes di mana Sumber diubah antara permintaan pengambilan pertama dan kedua:

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

Langkah demi langkah:

  • pertama kami membuat dan mendapatkan Sumber - dan menyimpan nilai ETag untuk penggunaan selanjutnya
  • maka kami mengemas kini Sumber yang sama
  • kirim permintaan GET baru, kali ini dengan header " If-None-Match " yang menentukan ETag yang sebelumnya kami simpan
  • on this second request, the server will return a 200 OK along with the full Resource, since the ETag value is no longer correct, as we updated the Resource in the meantime

Finally, the last test – which is not going to work because the functionality has not yet been implemented in Spring – is 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); }

Step by step:

  • we create a Resource
  • then retrieve it using the “If-Match” header specifying an incorrect ETag value – this is a conditional GET request
  • the server should return a 412 Precondition Failed

6. ETags Are Big

Kami hanya menggunakan ETag untuk operasi membaca. Terdapat RFC yang cuba menjelaskan bagaimana pelaksanaan harus menangani ETag pada operasi tulis - ini bukan standard, tetapi merupakan bacaan yang menarik.

Tentunya ada kemungkinan penggunaan lain dari mekanisme ETag, seperti untuk Mekanisme Penguncian Optimis serta menangani "Masalah Kemasukan yang Hilang" yang berkaitan.

Terdapat juga beberapa perangkap dan peringatan yang mungkin diketahui semasa menggunakan ETag.

7. Kesimpulannya

Artikel ini hanya menggaru permukaan dengan apa yang mungkin dengan Spring dan ETags.

Untuk pelaksanaan sepenuhnya perkhidmatan RESTful yang diaktifkan oleh ETag, bersama dengan ujian integrasi yang mengesahkan tingkah laku ETag, periksa projek GitHub.

REST bawah

Saya baru sahaja mengumumkan kursus Learn Spring yang baru , yang berfokus pada asas-asas Spring 5 dan Spring Boot 2:

>> SEMAK KURSUS