ETags for REST with Spring

ODPOČINOK Najlepšie

Práve som oznámil nové Naučte sa jar kurz zameraný na základy jari 5 a Spring Boot 2:

>> SKONTROLUJTE KURZ

1. Prehľad

Tento článok sa zameria na práca s ETags na jar, testovanie integrácie REST API a scenáre spotreby s zvlnenie.

2. REST a značky EAG

Z oficiálnej jarnej dokumentácie o podpore ETag:

ETag (značka entity) je hlavička odpovede HTTP vrátená webovým serverom kompatibilným s HTTP / 1.1, ktorá sa používa na určenie zmeny obsahu na danej adrese URL.

ETagy môžeme použiť na dve veci - ukladanie do pamäte cache a podmienené požiadavky. The Hodnotu EAG možno považovať za hash vypočítané z bajtov tela odpovede. Pretože služba pravdepodobne používa kryptografickú hashovaciu funkciu, aj najmenšia modifikácia tela drasticky zmení výstup a tým aj hodnotu ETag. To platí iba pre silné ETagy - protokol poskytuje tiež slabý Etag.

Pomocou Keby- * hlavička zmení štandardnú požiadavku GET na podmienené GET. Dva Keby- * hlavičky, ktoré sa používajú s ETags, sú „If-None-Match“ a „If-Match“ - každá s vlastnou sémantikou, ako sa o nej hovorí ďalej v tomto článku.

3. Komunikácia klient-server s zvlnenie

Jednoduchú komunikáciu medzi klientom a serverom zahŕňajúcu značky ETag môžeme rozdeliť do týchto krokov:

Najskôr klient zavolá REST API - odpoveď obsahuje hlavičku ETag ktoré budú uložené pre ďalšie použitie:

curl -H "Prijať: 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

Pre ďalšiu žiadosť klient uvedie Ak-Žiadny-Zhoda hlavička požiadavky s hodnotou ETag z predchádzajúceho kroku. Ak sa zdroj na serveri nezmenil, odpoveď nebude obsahovať žiadne telo a stavový kód z 304 - nezmenené:

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

Pred opätovným načítaním zdroja ho teraz zmeňme vykonaním aktualizácie:

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" Obsah-dĺžka: 0

Nakoniec pošleme poslednú žiadosť o opätovné získanie Foo. Pamätajte, že sme ju aktualizovali od poslednej žiadosti, takže predchádzajúca hodnota ETag by už nemala fungovať. Odpoveď bude obsahovať nové údaje a nový ETag, ktorý je opäť možné uložiť na ďalšie použitie:

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" Typ obsahu: application / json; charset = UTF-8 Dĺžka obsahu: 56

A máte to - EAGs vo voľnej prírode a šetriace šírku pásma.

4. Podpora ETag na jar

Podpora na jar: použitie ETag na jar je mimoriadne ľahké nastaviť a úplne transparentné pre aplikáciu. Podporu môžeme povoliť pridaním jednoduchého Filtrovať v web.xml:

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

Mapujeme filter na rovnaký vzor URI ako samotné rozhranie RESTful API. Samotný filter je štandardnou implementáciou funkčnosti ETag od jari 3.0.

Implementácia je plytká - aplikácia vypočíta ETag na základe odozvy, čo ušetrí šírku pásma, ale nie výkon servera.

Takže požiadavka, ktorá bude mať prospech z podpory ETag, bude stále spracovaná ako štandardná požiadavka, spotrebuje akýkoľvek zdroj, ktorý by bežne spotreboval (databázové pripojenia atď.) A podpora ETag bude kopaná až pred vrátením odpovede klientovi v.

V tom okamihu bude ETag vypočítaný z tela odpovede a nastavený na samotný zdroj; tiež, ak Ak-Žiadny-Zhoda hlavička bola nastavená na požiadavke, bude sa s ňou tiež nakladať.

Hlbšia implementácia mechanizmu ETag by mohla potenciálne poskytnúť oveľa väčšie výhody - napríklad obsluhovať niektoré požiadavky z vyrovnávacej pamäte a nemusieť vôbec vykonávať výpočet - implementácia by však určite nebola taká jednoduchá a zásuvná ako plytký prístup. popísané tu.

4.1. Konfigurácia založená na prostredí Java

Pozrime sa, ako by vyzerala konfigurácia založená na prostredí Java vyhlasovanie a ShallowEtagHeaderFilter fazuľa v našom jarnom kontexte:

@Bean public ShallowEtagHeaderFilter shallowEtagHeaderFilter () {vrátiť nový ShallowEtagHeaderFilter (); }

Majte na pamäti, že ak potrebujeme poskytnúť ďalšie konfigurácie filtra, môžeme namiesto toho vyhlásiť a FilterRegistrationBean inštancia:

@Bean public FilterRegistrationBean shallowEtagHeaderFilter () {FilterRegistrationBean filterRegistrationBean = nový FilterRegistrationBean (nový ShallowEtagHeaderFilter ()); filterRegistrationBean.addUrlPatterns ("/ foos / *"); filterRegistrationBean.setName ("etagFilter"); návrat filterRegistrationBean; }

Nakoniec, ak nepoužívame Spring Boot, môžeme filter nastaviť pomocou AbstractAnnotationConfigDispatcherServletInitializer‘S getServletFilters metóda.

4.2. Používanie ResponseEntity eTag () Metóda

Táto metóda bola zavedená v jarnom rámci 4.1 a môžeme ho použiť na riadenie hodnoty ETag, ktorú získa jeden koncový bod.

Predstavte si napríklad, že používame verziované entity ako mechanizmus blokovania optimizmu na prístup k informáciám o našej databáze.

Samotnú verziu môžeme použiť ako ETag na označenie, či bola entita zmenená:

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

Služba načíta zodpovedajúce 304-nezmenené stav, ak sa podmienená hlavička žiadosti zhoduje s údajmi do pamäte cache.

5. Testovanie ETags

Začnime jednoducho - musíme overiť, že odpoveď na jednoduchú požiadavku na získanie jedného zdroja skutočne vráti „ETag ” hlavička:

@Test public void givenResourceExists_whenRetrievingResource_thenEtagIsAlsoReturned () {// Daný reťazec uriOfResource = createAsUri (); // When Response findOneResponse = RestAssured.given (). hlavička ("Prijať", "aplikácia / json"). get (uriOfResource); // Potom assertNotNull (findOneResponse.getHeader ("ETag")); }

Ďalšie, overujeme šťastnú cestu správania ETag. Ak je žiadosť o získanie Zdroj zo servera používa správne ETag hodnotu, potom server nenačíta Zdroj:

@Test public void givenResourceWasRetrieved_whenRetrievingAgainWithEtag_thenNotModifiedReturned () {// Daný reťazec uriOfResource = createAsUri (); Odozva findOneResponse = RestAssured.given (). hlavička ("Prijať", "aplikácia / json"). get (uriOfResource); Reťazec etagValue = findOneResponse.getHeader (HttpHeaders.ETAG); // When Response secondFindOneResponse = RestAssured.given (). header ("Accept", "application / json"). headers ("If-None-Match", etagValue) .get (uriOfResource); // Potom assertTrue (secondFindOneResponse.getStatusCode () == 304); }

Krok za krokom:

  • vytvárame a načítame zdroj, skladovanie the ETag hodnotu
  • pošlite novú požiadavku na načítanie, tentokrát s „Ak-Žiadny-Zhoda"Hlavička s uvedením ETag predtým uložená hodnota
  • na túto druhú požiadavku server jednoducho vráti a 304 Nezmenené, pretože samotný zdroj skutočne nebol upravovaný medzi týmito dvoma operáciami vyhľadávania

Nakoniec overíme prípad, keď sa zdroj zmení medzi prvou a druhou požiadavkou na získanie:

@Test public void givenResourceWasRetrievedThenModified_whenRetrievingAgainWithEtag_thenResourceIsReturned () {// Daný reťazec uriOfResource = createAsUri (); Odozva findOneResponse = RestAssured.given (). hlavička ("Prijať", "aplikácia / json"). get (uriOfResource); Reťazec etagValue = findOneResponse.getHeader (HttpHeaders.ETAG); existingResource.setName (randomAlphabetic (6)); aktualizácia (existingResource); // When Response secondFindOneResponse = RestAssured.given (). header ("Accept", "application / json"). headers ("If-None-Match", etagValue) .get (uriOfResource); // Potom assertTrue (secondFindOneResponse.getStatusCode () == 200); }

Krok za krokom:

  • najskôr vytvoríme a načítame a Zdroj - a uložte ETag hodnota pre ďalšie použitie
  • potom aktualizujeme to isté Zdroj
  • pošlite novú žiadosť GET, tentokrát s „Ak-Žiadny-Zhoda"Hlavička s uvedením ETag ktoré sme predtým uložili
  • na túto druhú žiadosť server vráti a 200 OK spolu s úplným zdrojom, pretože ETag hodnota už nie je správna, pretože sme medzitým aktualizovali zdroj

Nakoniec posledný test - ktorý nebude fungovať, pretože funkčnosť ešte nebola implementovaná na jar - je podpora pre Keby-zápas Hlavička HTTP:

@Test public void givenResourceExists_whenRetrievedWithIfMatchIncorrectEtag_then412IsReceived () {// Vzhľadom na T existingResource = getApi (). Create (createNewEntity ()); // When String uriOfResource = baseUri + "/" + existingResource.getId (); Response findOneResponse = RestAssured.given (). Header ("Accept", "application / json"). hlavičky ("If-Match", randomAlphabetic (8)). get (uriOfResource); // Potom assertTrue (findOneResponse.getStatusCode () == 412); }

Krok za krokom:

  • vytvárame zdroj
  • potom ju načítajte pomocou „Keby-zápas”Špecifikuje nesprávnu hlavičku ETag hodnota - toto je podmienená požiadavka GET
  • server by mal vrátiť a 412 Podmienka zlyhala

6. ETagy sú veľké

Na operácie čítania sme použili iba značky ETag. Existuje RFC, ktorá sa snaží objasniť, ako by sa implementácie mali zaoberať značkami ETag pri operáciách zápisu - nie je to štandardné, ale je to zaujímavé čítanie.

Existujú samozrejme aj ďalšie možné použitia mechanizmu ETag, napríklad pre optimistický blokovací mechanizmus, ako aj na riešenie súvisiaceho „problému so stratou aktualizácie“.

Existuje tiež niekoľko známych potenciálnych úskalí a varovaní, ktoré by ste si mali uvedomiť pri používaní značiek ETag.

7. Záver

Tento článok poškriabal povrch iba tým, čo je možné pomocou Spring a ETags.

Ak chcete získať úplnú implementáciu služby RESTful s povoleným ETag, spolu s testami integrácie overujúcimi správanie ETag, pozrite si projekt GitHub.

REST spodok

Práve som oznámil nové Naučte sa jar kurz zameraný na základy jari 5 a Spring Boot 2:

>> SKONTROLUJTE KURZ

$config[zx-auto] not found$config[zx-overlay] not found