Рубрики
Без рубрики

Заголовки кэша в Spring MVC

Узнайте о кэшировании HTTP с помощью заголовка ответа управления кэшированием в Spring MVC.

Автор оригинала: baeldung.

1. Обзор

В этом уроке мы узнаем о кэшировании HTTP. Мы также рассмотрим различные способы реализации этого механизма между клиентом и приложением Spring MVC.

2. Внедрение кэширования HTTP

Когда мы открываем веб-страницу в браузере, она обычно загружает много ресурсов с веб-сервера:

Например, в этом примере браузеру необходимо загрузить три ресурса для одной /страницы входа . Обычно браузер выполняет несколько HTTP-запросов для каждой веб-страницы. Теперь, если мы запрашиваем такие страницы очень часто, это вызывает большой сетевой трафик и занимает больше времени для обслуживания этих страниц .

Чтобы снизить нагрузку на сеть, протокол HTTP позволяет браузерам кэшировать некоторые из этих ресурсов. Если эта опция включена, браузеры могут сохранять копию ресурса в локальном кэше. В результате браузеры могут обслуживать эти страницы из локального хранилища, а не запрашивать их по сети:

Веб-сервер может направить браузер на кэширование определенного ресурса, добавив в ответ заголовок Cache-Control .

Поскольку ресурсы кэшируются как локальная копия, существует риск предоставления устаревшего контента из браузера . Поэтому веб-серверы обычно добавляют время истечения срока действия в заголовок Cache-Control .

В следующих разделах мы добавим этот заголовок в ответ от контроллера Spring MVC. Позже мы также увидим API Spring для проверки кэшированных ресурсов на основе времени истечения срока действия.

3. Контроль кэша в ответе контроллера

3.1. Использование ResponseEntity

Самый простой способ сделать это – использовать класс CacheControl builder, предоставляемый 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);
}

Это добавит заголовок Cache-Control в ответ:

@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. Использование HttpServletResponse

Часто контроллерам необходимо возвращать имя представления из метода обработчика. Однако класс ResponseEntity не позволяет нам возвращать имя представления и одновременно обрабатывать тело запроса .

В качестве альтернативы, для таких контроллеров мы можем установить заголовок Cache-Control в 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";
}

Это также добавит заголовок Cache-Control в HTTP-ответ, аналогичный предыдущему разделу:

@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. Контроль кэша для статических ресурсов

Как правило, наше приложение Spring MVC обслуживает множество статических ресурсов , таких как файлы HTML, CSS и JS. Поскольку такие файлы потребляют много пропускной способности сети, поэтому для браузеров важно кэшировать их. Мы снова включим это с помощью заголовка Cache-Control в ответе.

Spring позволяет нам контролировать это поведение кэширования при сопоставлении ресурсов:

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

Это гарантирует, что все ресурсы , определенные в /ресурсы , будут возвращены с заголовком Cache-Control в ответе .

5. Контроль кэша в перехватчиках

Мы можем использовать перехватчики в нашем приложении Spring MVC для предварительной и последующей обработки каждого запроса. Это еще один заполнитель, с помощью которого мы можем контролировать поведение кэширования приложения.

Теперь вместо реализации пользовательского перехватчика мы будем использовать WebContentInterceptor , предоставленный Spring :

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

Здесь мы зарегистрировали WebContentInterceptor и добавили заголовок Cache-Control , аналогичный нескольким последним разделам. Примечательно, что мы можем добавлять разные Cache-Control заголовки для разных шаблонов URL-адресов.

В приведенном выше примере для всех запросов, начинающихся с /login , мы добавим этот заголовок:

@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. Проверка кэша в Spring MVC

До сих пор мы обсуждали различные способы включения заголовка Cache-Control в ответ. Это указывает на то, что клиенты или браузеры должны кэшировать ресурсы на основе свойств конфигурации, таких как максимальный возраст .

Как правило, хорошей идеей является добавление времени истечения срока действия кэша для каждого ресурса . В результате браузеры могут избежать использования ресурсов с истекшим сроком действия из кэша.

Хотя браузеры всегда должны проверять срок действия, может не потребоваться каждый раз повторно извлекать ресурс. Если браузер может проверить, что ресурс не изменился на сервере, он может продолжать обслуживать его кэшированную версию. И для этой цели HTTP предоставляет нам два заголовка ответа:

  1. Etag – заголовок HTTP – ответа, в котором хранится уникальное хэш-значение для определения того, изменился ли кэшированный ресурс на сервере-соответствующий заголовок If-None-Match запроса должен содержать последнее значение Etag
  2. LastModified – заголовок HTTP – ответа, в котором хранится единица времени, когда ресурс был обновлен в последний раз-соответствующий Если-Неизмененный-Так Как заголовок запроса должен содержать дату последнего изменения

Мы можем использовать любой из этих заголовков, чтобы проверить, нужно ли повторно извлекать ресурс с истекшим сроком действия. После проверки заголовков сервер может либо повторно отправить ресурс, либо отправить HTTP-код 304, чтобы указать отсутствие изменений . В последнем случае браузеры могут продолжать использовать кэшированный ресурс.

Заголовок Последнее изменение может хранить только временные интервалы с точностью до секунд. Это может быть ограничением в тех случаях, когда требуется более короткий срок действия. По этой причине вместо этого рекомендуется использовать Etag . Поскольку в заголовке Etag хранится значение хэша , можно создать уникальный хэш с более тонкими интервалами, такими как наносекунды.

Тем не менее, давайте посмотрим, как это выглядит при использовании Последнее изменение.

Spring предоставляет некоторые служебные методы для проверки того, содержит ли запрос заголовок истечения срока действия или нет:

@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 предоставляет метод checkNotModified() для проверки того, был ли ресурс изменен с момента последнего запроса:

@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. Заключение

В этой статье мы узнали о кэшировании HTTP с помощью заголовка Cache-Control response в Spring MVC. Мы можем либо добавить заголовок в ответ контроллера, используя класс ResponseEntity , либо с помощью сопоставления ресурсов для статических ресурсов.

Мы также можем добавить этот заголовок для определенных шаблонов URL-адресов, используя перехватчики Spring.

Как всегда, код доступен на GitHub .