En-têtes de cache dans Spring MVC

1. Vue d'ensemble

Dans ce didacticiel, nous allons découvrir la mise en cache HTTP. Nous examinerons également différentes manières d'implémenter ce mécanisme entre un client et une application Spring MVC.

2. Présentation de la mise en cache HTTP

Lorsque nous ouvrons une page Web sur un navigateur, il télécharge généralement beaucoup de ressources à partir du serveur Web:

Par exemple, dans cet exemple, un navigateur doit télécharger trois ressources pour une / page de connexion . Il est courant pour un navigateur d'effectuer plusieurs requêtes HTTP pour chaque page Web. Maintenant, si nous demandons de telles pages très fréquemment, cela entraîne beaucoup de trafic réseau et prend plus de temps à servir ces pages .

Pour réduire la charge du réseau, le protocole HTTP permet aux navigateurs de mettre en cache certaines de ces ressources. S'il est activé, les navigateurs peuvent enregistrer une copie d'une ressource dans le cache local. En conséquence, les navigateurs peuvent servir ces pages à partir du stockage local au lieu de les demander sur le réseau:

Un serveur Web peut demander au navigateur de mettre en cache une ressource particulière en ajoutant un en - tête Cache-Control dans la réponse.

Étant donné que les ressources sont mises en cache en tant que copie locale, il existe un risque de diffuser du contenu périmé à partir du navigateur . Par conséquent, les serveurs Web ajoutent généralement une heure d'expiration dans l'en - tête Cache-Control .

Dans les sections suivantes, nous ajouterons cet en-tête dans une réponse du contrôleur Spring MVC. Plus tard, nous verrons également les API Spring pour valider les ressources mises en cache en fonction de l'heure d'expiration.

3. Cache-Control dans la réponse du contrôleur

3.1. Utilisation de ResponseEntity

Le moyen le plus simple de le faire est d' utiliser la classe de générateur CacheControl fournie par 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); }

Cela ajoutera un en - tête Cache-Control dans la réponse:

@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. Utilisation de HttpServletResponse

Souvent, les contrôleurs doivent renvoyer le nom de la vue à partir de la méthode du gestionnaire. Cependant, la classe ResponseEntity ne nous permet pas de renvoyer le nom de la vue et de traiter le corps de la requête en même temps .

Alternativement, pour de tels contrôleurs, nous pouvons définir directement l'en - tête Cache-Control dans 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"; }

Cela ajoutera également un en - tête Cache-Control dans la réponse HTTP similaire à la dernière section:

@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. Cache-Control pour les ressources statiques

En règle générale, notre application Spring MVC sert de nombreuses ressources statiques telles que les fichiers HTML, CSS et JS. Étant donné que ces fichiers consomment beaucoup de bande passante réseau, il est donc important que les navigateurs les mettent en cache. Nous l'activerons à nouveau avec l'en - tête Cache-Control dans la réponse.

Spring nous permet de contrôler ce comportement de mise en cache dans le mappage de ressources:

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

Cela garantit que toutes les ressources définies sous / resources sont renvoyées avec un en - tête Cache-Control dans la réponse .

5. Contrôle du cache dans les intercepteurs

Nous pouvons utiliser des intercepteurs dans notre application Spring MVC pour effectuer un pré et un post-traitement pour chaque requête. Il s'agit d'un autre espace réservé où nous pouvons contrôler le comportement de mise en cache de l'application.

Maintenant, au lieu d'implémenter un intercepteur personnalisé, nous utiliserons le WebContentInterceptor fourni par Spring :

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

Ici, nous avons enregistré le WebContentInterceptor et ajouté l'en - tête Cache-Control similaire aux dernières sections. Notamment, nous pouvons ajouter différents en - têtes Cache-Control pour différents modèles d'URL.

Dans l'exemple ci-dessus, pour toutes les requêtes commençant par / login , nous ajouterons cet en-tête:

@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. Validation du cache dans Spring MVC

Jusqu'à présent, nous avons discuté de diverses façons d'inclure un en - tête Cache-Control dans la réponse. Cela indique aux clients ou aux navigateurs de mettre en cache les ressources en fonction des propriétés de configuration telles que max-age .

C'est généralement une bonne idée d'ajouter une heure d'expiration du cache à chaque ressource . Par conséquent, les navigateurs peuvent éviter de diffuser des ressources expirées à partir du cache.

Bien que les navigateurs doivent toujours vérifier l'expiration, il peut ne pas être nécessaire de récupérer à chaque fois la ressource. Si un navigateur peut valider qu'une ressource n'a pas changé sur le serveur, il peut continuer à en servir la version mise en cache. Et pour cela, HTTP nous fournit deux en-têtes de réponse:

  1. Etag - un en-tête de réponse HTTP qui stocke une valeur de hachage unique pour déterminer si une ressource mise en cache a changé sur le serveur - un en - tête de demande If-None-Match correspondant doit porter la dernière valeur Etag
  2. LastModified - un en-tête de réponse HTTP qui stocke une unité de temps lors de la dernière mise à jour de la ressource - un en - tête de demande If-Unmodified-Since correspondant doit porter la date de la dernière modification

Nous pouvons utiliser l'un de ces en-têtes pour vérifier si une ressource expirée doit être à nouveau récupérée. Après avoir validé les en-têtes, le serveur peut renvoyer la ressource ou envoyer un code HTTP 304 pour signifier aucun changement . Pour ce dernier scénario, les navigateurs peuvent continuer à utiliser la ressource mise en 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

Dans cet article, nous avons découvert la mise en cache HTTP à l'aide de l'en - tête de réponse Cache-Control dans Spring MVC. Nous pouvons soit ajouter l'en-tête dans la réponse du contrôleur à l'aide de la classe ResponseEntity ou via le mappage de ressources pour les ressources statiques.

Nous pouvons également ajouter cet en-tête pour des modèles d'URL particuliers à l'aide d'intercepteurs Spring.

Comme toujours, le code est disponible sur sur GitHub.