Intestazioni della cache in Spring MVC

1. Panoramica

In questo tutorial, impareremo a conoscere la cache HTTP. Esamineremo anche vari modi per implementare questo meccanismo tra un client e un'applicazione Spring MVC.

2. Introduzione alla cache HTTP

Quando apriamo una pagina web su un browser, di solito scarica molte risorse dal server web:

Ad esempio, in questo esempio, un browser deve scaricare tre risorse per una pagina / login . È normale che un browser effettui più richieste HTTP per ogni pagina web. Ora, se richiediamo tali pagine molto frequentemente, causa molto traffico di rete e richiede più tempo per servire queste pagine .

Per ridurre il carico di rete, il protocollo HTTP consente ai browser di memorizzare nella cache alcune di queste risorse. Se abilitato, i browser possono salvare una copia di una risorsa nella cache locale. Di conseguenza, i browser possono servire queste pagine dalla memoria locale invece di richiederle sulla rete:

Un server web può indicare al browser di memorizzare nella cache una particolare risorsa aggiungendo un'intestazione Cache-Control nella risposta.

Poiché le risorse vengono memorizzate nella cache come copia locale, esiste il rischio di fornire contenuti obsoleti dal browser . Pertanto, i server Web di solito aggiungono una scadenza nell'intestazione Cache-Control .

Nelle sezioni seguenti, aggiungeremo questa intestazione in una risposta dal controller Spring MVC. Successivamente, vedremo anche le API Spring per convalidare le risorse memorizzate nella cache in base alla scadenza.

3. Controllo cache nella risposta del controller

3.1. Utilizzo di ResponseEntity

Il modo più semplice per farlo è utilizzare la classe builder CacheControl fornita da Spring :

@GetMapping("/hello/{name}") @ResponseBody public ResponseEntity hello(@PathVariable String name) { CacheControl cacheControl = CacheControl.maxAge(60, TimeUnit.SECONDS) .noTransform() .mustRevalidate(); return ResponseEntity.ok() .cacheControl(cacheControl) .body("Hello " + name); }

Questo aggiungerà un'intestazione Cache-Control nella risposta:

@Test void whenHome_thenReturnCacheHeader() throws Exception { this.mockMvc.perform(MockMvcRequestBuilders.get("/hello/baeldung")) .andDo(MockMvcResultHandlers.print()) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.header() .string("Cache-Control","max-age=60, must-revalidate, no-transform")); }

3.2. Utilizzando HttpServletResponse

Spesso, i controller devono restituire il nome della vista dal metodo del gestore. Tuttavia, la classe ResponseEntity non ci consente di restituire il nome della vista e allo stesso tempo trattare il corpo della richiesta .

In alternativa, per tali controller possiamo impostare direttamente l' intestazione Cache-Control in HttpServletResponse :

@GetMapping(value = "/home/{name}") public String home(@PathVariable String name, final HttpServletResponse response) { response.addHeader("Cache-Control", "max-age=60, must-revalidate, no-transform"); return "home"; }

Questo aggiungerà anche un'intestazione Cache-Control nella risposta HTTP simile all'ultima sezione:

@Test void whenHome_thenReturnCacheHeader() throws Exception { this.mockMvc.perform(MockMvcRequestBuilders.get("/home/baeldung")) .andDo(MockMvcResultHandlers.print()) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.header() .string("Cache-Control","max-age=60, must-revalidate, no-transform")) .andExpect(MockMvcResultMatchers.view().name("home")); }

4. Controllo cache per risorse statiche

In generale, la nostra applicazione Spring MVC serve molte risorse statiche come file HTML, CSS e JS. Poiché tali file consumano molta larghezza di banda di rete, è importante che i browser li memorizzino nella cache. Lo abiliteremo di nuovo con l' intestazione Cache-Control nella risposta.

Spring ci consente di controllare questo comportamento di caching nella mappatura delle risorse:

@Override public void addResourceHandlers(final ResourceHandlerRegistry registry) { registry.addResourceHandler("/resources/**").addResourceLocations("/resources/") .setCacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS) .noTransform() .mustRevalidate()); }

Ciò garantisce che tutte le risorse definite in / resources vengano restituite con un'intestazione Cache-Control nella risposta .

5. Controllo della cache negli intercettori

Possiamo utilizzare gli intercettori nella nostra applicazione Spring MVC per eseguire una pre e post-elaborazione per ogni richiesta. Questo è un altro segnaposto in cui possiamo controllare il comportamento di memorizzazione nella cache dell'applicazione.

Ora invece di implementare un intercettore personalizzato, useremo il WebContentInterceptor fornito da Spring :

@Override public void addInterceptors(InterceptorRegistry registry) { WebContentInterceptor interceptor = new WebContentInterceptor(); interceptor.addCacheMapping(CacheControl.maxAge(60, TimeUnit.SECONDS) .noTransform() .mustRevalidate(), "/login/*"); registry.addInterceptor(interceptor); }

Qui, abbiamo registrato WebContentInterceptor e aggiunto l' intestazione Cache-Control simile alle ultime sezioni. In particolare, possiamo aggiungere diverse intestazioni Cache-Control per diversi pattern URL.

Nell'esempio sopra, per tutte le richieste che iniziano con / login , aggiungeremo questa intestazione:

@Test void whenInterceptor_thenReturnCacheHeader() throws Exception { this.mockMvc.perform(MockMvcRequestBuilders.get("/login/baeldung")) .andDo(MockMvcResultHandlers.print()) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.header() .string("Cache-Control","max-age=60, must-revalidate, no-transform")); }

6. Convalida della cache in Spring MVC

Finora, abbiamo discusso vari modi per includere un'intestazione Cache-Control nella risposta. Indica i client o browser per memorizzare nella cache le risorse in base alle proprietà di configurazione come max-age .

In genere è una buona idea aggiungere un tempo di scadenza della cache con ogni risorsa . Di conseguenza, i browser possono evitare di servire le risorse scadute dalla cache.

Sebbene i browser debbano sempre verificare la scadenza, potrebbe non essere necessario recuperare la risorsa ogni volta. Se un browser può convalidare che una risorsa non è stata modificata sul server, può continuare a fornire la versione memorizzata nella cache. E a questo scopo, HTTP ci fornisce due intestazioni di risposta:

  1. Etag : un'intestazione di risposta HTTP che memorizza un valore hash univoco per determinare se una risorsa memorizzata nella cache è cambiata sul server: un'intestazione di richiesta If-None-Match corrispondente deve contenere l'ultimo valore Etag
  2. LastModified - un'intestazione di risposta HTTP che memorizza un'unità di tempo in cui la risorsa è stata aggiornata l'ultima volta - un'intestazione della richiesta If-Unmodified-Since corrispondente deve contenere la data dell'ultima modifica

Possiamo usare una di queste intestazioni per verificare se una risorsa scaduta deve essere recuperata. Dopo aver convalidato le intestazioni, il server può inviare nuovamente la risorsa o inviare un codice HTTP 304 per indicare l'assenza di modifiche . Per quest'ultimo scenario, i browser possono continuare a utilizzare la risorsa memorizzata nella cache.

The LastModified header can only store time intervals up to seconds precision. This can be a limitation in cases where a shorter expiry is required. For this reason, it's recommended to use Etag instead. Since Etag header stores a hash value, it's possible to create a unique hash up to more finer intervals like nanoseconds.

That said, let's check out what it looks like to use LastModified.

Spring provides some utility methods to check if the request contains an expiration header or not:

@GetMapping(value = "/productInfo/{name}") public ResponseEntity validate(@PathVariable String name, WebRequest request) { ZoneId zoneId = ZoneId.of("GMT"); long lastModifiedTimestamp = LocalDateTime.of(2020, 02, 4, 19, 57, 45) .atZone(zoneId).toInstant().toEpochMilli(); if (request.checkNotModified(lastModifiedTimestamp)) { return ResponseEntity.status(304).build(); } return ResponseEntity.ok().body("Hello " + name); }

Spring provides the checkNotModified() method to check if a resource has been modified since the last request:

@Test void whenValidate_thenReturnCacheHeader() throws Exception { HttpHeaders headers = new HttpHeaders(); headers.add(IF_UNMODIFIED_SINCE, "Tue, 04 Feb 2020 19:57:25 GMT"); this.mockMvc.perform(MockMvcRequestBuilders.get("/productInfo/baeldung").headers(headers)) .andDo(MockMvcResultHandlers.print()) .andExpect(MockMvcResultMatchers.status().is(304)); }

7. Conclusion

In questo articolo, abbiamo appreso della memorizzazione nella cache HTTP usando l' intestazione della risposta Cache-Control in Spring MVC. Possiamo aggiungere l'intestazione nella risposta del controller utilizzando la classe ResponseEntity o tramite la mappatura delle risorse per le risorse statiche.

Possiamo anche aggiungere questa intestazione per particolari pattern URL utilizzando gli intercettori Spring.

Come sempre, il codice è disponibile su GitHub.