Cache Header di Spring MVC

1. Ikhtisar

Dalam tutorial ini, kita akan belajar tentang cache HTTP. Kami juga akan melihat berbagai cara untuk mengimplementasikan mekanisme ini antara klien dan aplikasi Spring MVC.

2. Memperkenalkan Cache HTTP

Saat kami membuka halaman web di browser, biasanya halaman tersebut mendownload banyak sumber daya dari server web:

Misalnya, dalam contoh ini, browser perlu mengunduh tiga sumber daya untuk satu / halaman login . Browser biasa membuat beberapa permintaan HTTP untuk setiap halaman web. Sekarang, jika kami sangat sering meminta halaman seperti itu, ini menyebabkan banyak lalu lintas jaringan dan membutuhkan waktu lebih lama untuk menyajikan halaman tersebut .

Untuk mengurangi beban jaringan, protokol HTTP mengizinkan browser untuk menyimpan beberapa sumber daya ini ke dalam cache. Jika diaktifkan, browser dapat menyimpan salinan sumber daya di cache lokal. Hasilnya, browser dapat menyajikan halaman ini dari penyimpanan lokal alih-alih memintanya melalui jaringan:

Server web dapat mengarahkan browser untuk menyimpan sumber daya tertentu dengan menambahkan header Kontrol-Cache sebagai respons.

Karena sumber daya di-cache sebagai salinan lokal, ada risiko menyajikan konten usang dari browser . Oleh karena itu, server web biasanya menambahkan waktu kedaluwarsa di header Cache-Control .

Di bagian berikut, kami akan menambahkan header ini sebagai respons dari pengontrol Spring MVC. Nanti, kita juga akan melihat Spring API untuk memvalidasi sumber daya yang disimpan dalam cache berdasarkan waktu kedaluwarsa.

3. Kontrol-Cache dalam Respons Pengontrol

3.1. Menggunakan ResponseEntity

Cara paling mudah untuk melakukannya adalah dengan menggunakan kelas pembuat CacheControl yang disediakan oleh 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); }

Ini akan menambahkan header Cache-Control sebagai respons:

@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. Menggunakan HttpServletResponse

Seringkali, pengontrol perlu mengembalikan nama tampilan dari metode penangan. Namun, kelas ResponseEntity tidak mengizinkan kita untuk mengembalikan nama tampilan dan menangani badan permintaan pada saat yang bersamaan .

Alternatifnya, untuk pengontrol seperti itu kita dapat mengatur header Cache-Control di HttpServletResponse secara langsung:

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

Ini juga akan menambahkan header Cache-Control dalam respons HTTP yang mirip dengan bagian terakhir:

@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. Kontrol-Cache untuk Sumber Daya Statis

Secara umum, aplikasi Spring MVC kami menyediakan banyak resource statis seperti file HTML, CSS, dan JS. Karena file semacam itu menghabiskan banyak bandwidth jaringan, jadi penting bagi browser untuk menyimpannya dalam cache. Kami akan mengaktifkan ini lagi dengan header Cache-Control sebagai respons.

Spring memungkinkan kami untuk mengontrol perilaku caching ini dalam pemetaan sumber daya:

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

Ini memastikan bahwa semua resource yang ditentukan di bawah / resources dikembalikan dengan header Cache-Control sebagai respons .

5. Cache-Control di Interceptors

Kita dapat menggunakan interseptor di aplikasi Spring MVC untuk melakukan beberapa pemrosesan sebelum dan sesudah untuk setiap permintaan. Ini adalah placeholder lain tempat kita dapat mengontrol perilaku cache aplikasi.

Sekarang alih-alih menerapkan pencegat khusus, kita akan menggunakan WebContentInterceptor yang disediakan oleh Spring :

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

Di sini, kami mendaftarkan WebContentInterceptor dan menambahkan header Cache-Control yang mirip dengan beberapa bagian terakhir. Khususnya, kita dapat menambahkan header Cache-Control yang berbeda untuk pola URL yang berbeda.

Dalam contoh di atas, untuk semua permintaan yang dimulai dengan / login , kami akan menambahkan header ini:

@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. Validasi Cache di Spring MVC

Sejauh ini, kita telah membahas berbagai cara untuk menyertakan header Cache-Control dalam responsnya. Ini menunjukkan klien atau browser untuk menyimpan sumber daya berdasarkan properti konfigurasi seperti usia-maks .

Biasanya merupakan ide yang bagus untuk menambahkan waktu kedaluwarsa cache dengan setiap sumber daya . Akibatnya, browser dapat menghindari menyajikan sumber daya yang kedaluwarsa dari cache.

Meskipun browser harus selalu memeriksa kedaluwarsa, mungkin tidak perlu mengambil kembali sumber daya setiap saat. Jika browser dapat memvalidasi bahwa sumber daya tidak berubah di server, browser dapat terus menyajikan versi cache-nya. Dan untuk tujuan ini, HTTP memberi kita dua header respons:

  1. Etag - header respons HTTP yang menyimpan nilai hash unik untuk menentukan apakah sumber daya yang di-cache telah berubah di server - header permintaan If-None-Match yang sesuai harus membawa nilai Etag terakhir
  2. LastModified - header respons HTTP yang menyimpan unit waktu saat sumber daya terakhir diperbarui - header permintaan If-Unmodified-Because yang sesuai harus membawa tanggal terakhir diubah

Kita dapat menggunakan salah satu dari tajuk ini untuk memeriksa apakah sumber daya yang kedaluwarsa perlu diambil ulang. Setelah memvalidasi header, server dapat mengirim ulang sumber daya atau mengirim kode HTTP 304 untuk menandakan tidak ada perubahan . Untuk skenario terakhir, browser dapat terus menggunakan sumber daya yang di-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

Di artikel ini, kita belajar tentang cache HTTP dengan menggunakan header respons Cache-Control di Spring MVC. Kita bisa menambahkan header dalam respon pengontrol menggunakan kelas ResponseEntity atau melalui pemetaan sumber daya untuk sumber daya statis.

Kami juga dapat menambahkan tajuk ini untuk pola URL tertentu menggunakan interseptor Spring.

Seperti biasa, kode tersedia di GitHub.