ETags for REST med Spring

REST Top

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN

1. Oversigt

Denne artikel vil fokusere på arbejder med ETags om foråret, integrationstest af REST API og forbrugsscenarier med krølle.

2. REST og ETags

Fra den officielle foråret dokumentation om ETag support:

Et ETag (enhedsmærke) er et HTTP-svarhoved, der returneres af en HTTP / 1.1-kompatibel webserver, der bruges til at bestemme ændring i indhold på en given URL.

Vi kan bruge ETags til to ting - caching og betingede anmodninger. Det ETag-værdi kan betragtes som en hash beregnet ud af bytes i responslegemet. Fordi tjenesten sandsynligvis bruger en kryptografisk hash-funktion, vil selv den mindste ændring af kroppen drastisk ændre output og dermed værdien af ​​ETag. Dette gælder kun for stærke ETags - protokollen giver også en svag Etag.

Brug af en Hvis-* header forvandler en standard GET-anmodning til en betinget GET. De to Hvis-* overskrifter, der bruger med ETags, er "If-None-Match" og "If-Match" - hver med sin egen semantik som beskrevet senere i denne artikel.

3. Klient-server kommunikation med krølle

Vi kan nedbryde en simpel klient-server-kommunikation, der involverer ETags, i trinene:

For det første foretager klienten et REST API-opkald - Svaret inkluderer ETag-overskriften der gemmes til videre brug:

krølle -H "Accepter: applikation / 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

For den næste anmodning inkluderer klienten Hvis-ingen-kamp anmodningsoverskrift med ETag-værdien fra det foregående trin. Hvis ressourcen ikke har ændret sig på serveren, indeholder svaret ingen brødtekst og en statuskode af 304 - Ikke ændret:

krølle -H "Accepter: anvendelse / json" -H 'Hvis-ingen-match: "f88dd058fe004909615a64f01be66a7"' -i // localhost: 8080 / spring-boot-rest / foos / 1
HTTP / 1.1 304 Ikke ændret ETag: "f88dd058fe004909615a64f01be66a7"

Lad os ændre det, inden vi henter ressourcen igen, ved at udføre en opdatering:

krølle -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" Indhold-længde: 0

Endelig sender vi den sidste anmodning om at hente Foo igen. Husk, at vi har opdateret det siden sidste gang, vi anmodede om det, så den tidligere ETag-værdi skulle ikke længere fungere. Svaret indeholder de nye data og en ny ETag, som igen kan lagres til videre brug:

krølle -H "Accepter: anvendelse / json" -H 'Hvis-ingen-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

Og der har du det - ETags i naturen og sparer båndbredde.

4. ETag Support om foråret

På Spring Support: Brug af ETag i Spring er ekstremt let at opsætte og fuldstændig gennemsigtig til applikationen. Vi kan aktivere supporten ved at tilføje en simpel Filter i web.xml:

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

Vi kortlægger filteret på det samme URI-mønster som selve RESTful API. Selve filteret er standardimplementeringen af ​​ETag-funktionalitet siden Spring 3.0.

Implementeringen er lav - applikationen beregner ETag baseret på svaret, hvilket sparer båndbredde, men ikke serverens ydeevne.

Så en anmodning, der vil drage fordel af ETag-understøttelsen, behandles stadig som en standardanmodning, forbruge enhver ressource, som den normalt ville forbruge (databaseforbindelser osv.), Og kun før svaret returneres til klienten, vil ETag support sparke i.

På det tidspunkt beregnes ETag ud af responsorganet og indstilles på selve ressourcen; også, hvis Hvis-ingen-kamp header blev indstillet på anmodningen, det håndteres også.

En dybere implementering af ETag-mekanismen kan potentielt give meget større fordele - såsom at betjene nogle anmodninger fra cachen og slet ikke behøver at udføre beregningen - men implementeringen vil bestemt ikke være så enkel eller så pluggbar som den lave tilgang beskrevet her.

4.1. Java-baseret konfiguration

Lad os se, hvordan den Java-baserede konfiguration vil se ud af erklærer en ShallowEtagHeaderFilter bønne i vores forårssammenhæng:

@Bean public ShallowEtagHeaderFilter shallowEtagHeaderFilter () {returner ny ShallowEtagHeaderFilter (); }

Husk, at hvis vi har brug for yderligere filterkonfigurationer, kan vi i stedet erklære en FilterRegistrationBean eksempel:

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

Endelig, hvis vi ikke bruger Spring Boot, kan vi opsætte filteret ved hjælp af AbstractAnnotationConfigDispatcherServletInitializer'S getServletFilters metode.

4.2. Brug af ResponseEntity's eTag () Metode

Denne metode blev introduceret i forårets ramme 4.1, og vi kan bruge den til at kontrollere ETag-værdien, som et enkelt slutpunkt henter.

Forestil dig for eksempel, at vi bruger versionerede enheder som en Optimist Locking-mekanisme til at få adgang til vores databaseoplysninger.

Vi kan bruge selve versionen som ETag til at angive, om enheden er blevet ændret:

@GetMapping (værdi = "/ {id} / custom-etag") offentlig ResponseEntity findByIdWithCustomEtag (@PathVariable ("id") endelig Lang id) {// ... Foo foo = ... return ResponseEntity.ok (). eTag (Long.toString (foo.getVersion ())). body (foo); }

Tjenesten henter det tilsvarende 304-Ikke ændret angive, om anmodningens betingede overskrift matcher cachedataene.

5. Test af ETags

Lad os starte simpelt - vi er nødt til at kontrollere, at svaret på en simpel anmodning, der henter en enkelt ressource, faktisk returnerer “ETag ” header:

@Test offentlig ugyldighed givenResourceExists_whenRetrievingResource_thenEtagIsAlsoReturned () {// givet streng uriOfResource = createAsUri (); // When Response findOneResponse = RestAssured.given (). header ("Accepter", "application / json"). get (uriOfResource); // Derefter assertNotNull (findOneResponse.getHeader ("ETag")); }

Næste, vi bekræfter den glade vej for ETag-opførelsen. Hvis anmodningen om at hente Ressource fra serveren bruger det korrekte ETag værdi, så henter serveren ikke ressourcen:

@Test offentlig ugyldighed givenResourceWasRetrieved_whenRetrievingAgainWithEtag_thenNotModifiedReturned () {// givet streng uriOfResource = createAsUri (); Svar findOneResponse = RestAssured.given (). header ("Accepter", "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); // Så assertTrue (secondFindOneResponse.getStatusCode () == 304); }

Trin for trin:

  • vi opretter og henter en ressource, opbevaring det ETag værdi
  • send en ny anmodning om hentning, denne gang med “Hvis-ingen-kamp”Header, der angiver ETag værdi, der tidligere er gemt
  • på denne anden anmodning returnerer serveren simpelthen en 304 Ikke ændret, da selve ressourcen faktisk ikke er blevet ændret mellem de to hentningsoperationer

Endelig bekræfter vi sagen, hvor ressourcen ændres mellem den første og den anden hentningsanmodning:

@Test offentlig ugyldighed givenResourceWasRetrievedThenModified_whenRetrievingAgainWithEtag_thenResourceIsReturned () {// givet streng uriOfResource = createAsUri (); Svar findOneResponse = RestAssured.given (). header ("Accepter", "application / json"). get (uriOfResource); String etagValue = findOneResponse.getHeader (HttpHeaders.ETAG); existingResource.setName (randomAlphabetic (6)); opdatering (eksisterende ressource); // When Response secondFindOneResponse = RestAssured.given (). header ("Accept", "application / json"). headers ("If-None-Match", etagValue) .get (uriOfResource); // Derefter assertTrue (secondFindOneResponse.getStatusCode () == 200); }

Trin for trin:

  • vi opretter og henter først en Ressource - og opbevar ETag værdi til yderligere brug
  • så opdaterer vi det samme Ressource
  • send en ny GET-anmodning, denne gang med “Hvis-ingen-kamp”Header, der angiver ETag som vi tidligere har gemt
  • på denne anden anmodning returnerer serveren a 200 OK sammen med den fulde ressource, siden ETag værdi er ikke længere korrekt, da vi i mellemtiden opdaterede ressourcen

Endelig er den sidste test - som ikke fungerer, fordi funktionaliteten endnu ikke er implementeret i foråret - er støtten til Hvis-match HTTP-overskrift:

@Test offentlig ugyldighed givenResourceExists_whenRetrievedWithIfMatchIncorrectEtag_then412IsReceived () {// givet T eksisterendeResource = getApi (). Opret (createNewEntity ()); // When String uriOfResource = baseUri + "/" + existingResource.getId (); Svar findOneResponse = RestAssured.given (). Header ("Accept", "application / json"). headers ("If-Match", randomAlphabetic (8)). get (uriOfResource); // Derefter assertTrue (findOneResponse.getStatusCode () == 412); }

Trin for trin:

  • vi opretter en ressource
  • derefter hente den ved hjælp af “Hvis-match”Header, der angiver en forkert ETag værdi - dette er en betinget GET-anmodning
  • serveren skal returnere a 412 Forudsætning mislykkedes

6. ETags er store

Vi har kun brugt ETags til læsning. Der findes en RFC, der forsøger at afklare, hvordan implementeringer skal håndtere ETags om skriveoperationer - dette er ikke standard, men er en interessant læsning.

Der er naturligvis andre mulige anvendelser af ETag-mekanismen, såsom til en optimistisk låsemekanisme såvel som at håndtere det relaterede "Lost Update Problem".

Der er også flere kendte potentielle faldgruber og advarsler, du skal være opmærksom på, når du bruger ETags.

7. Konklusion

Denne artikel ridsede kun overfladen med, hvad der er muligt med Spring og ETags.

For en fuld implementering af en ETag-aktiveret RESTful-service sammen med integrationstest, der verificerer ETag-opførslen, skal du tjekke GitHub-projektet.

REST bunden

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN