Spring Web MVC и данные HTTP запросов. Часть вторая.

В первой части статьи о работе с HTTP в Spring Web MVC я писал о заголовках и особых методах запроса. В этой статье я продолжу тему HTTP и Web MVC.

Кэширование

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

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

Первым механизм — заголовок Cache-Control. Он указывает клиенту, можно ли кэшировать данный ресурс и на какой срок. В Spring Web MVC можно установить этот заголовок, возвращая особый объект ответа: ResponseEntity. ResponseEntity это нечто среднее между HttpServletResponse и простым возвратом нужных данных: с одной стороны с помощью ResponseEntity можно довольно подробно настроить посылаемый клиенту ответ, хоть это будет и не так удобно, как просто возврат ваших данных, с другой стороны это более высокоуровневый и, следовательно, более ограниченный API, чем HttpServletResponse. В моём случае я буду использовать ResponseEntity для установки заголовков кэширования:

Объект CacheControl позволяет настроить параметры кэширования: ответ можно закэшировать на 1 день и его можно кэшировать в публичных прокси. Потом я добавляю этот объект к ответу и в заголовках ответа автоматически появится заголовок Cache-Control. Мой заголовок говорит клиенту, что ответ можно запомнить и хранить до примерно 10 утра по GMT 4го мая. Стоит отметить, что этот заголовок разрешает клиенту кэширование, но не обязывает его кэшировать.

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

В Spring Web MVC значение ETag отправляется так же с помощью ResponeEntity:

При запросе без дополнительных заголовков Spring Web MVC просто вставит наш ETag в заголовки ответа. Однако, если мы отправим запрос с заголовком If-None-Match, значение которого будет совпадать с ETag в ResponseEntity, Spring Web MVC автоматически вернёт 304 Not modified и не будет передавать клиенту ответ. Недостатком такого подхода является тот факт, что со стороны сервера ответ всё таки подготавливается и ресурсы на него тратятся. Таким образом экономится только полоса и время на передачу, но не ресурсы сервера.

Однако, можно вручную прерывать обработку в случае совпадений ETag или достаточной свежести данных и таким образом экономить ресурсы сервера. Для этого используется объект WebRequest, являющийся более человечным аналогом низкоуровневого HttpServletRequest.

Используя WebRequest я проверяю сразу два заголовка. Первый, If-Modified-Since, указывает мне, что я бы хотел получить новые данные, только если они изменились с указанного момента времени. Второй, If-None-Match, проверяет значение ETag. В случае выполнения одного из условий я возвращаю null, что является указанием вернуть 304 Not Modified. И только если условия не выполнены, я формирую полноценный ответ и отправляю его. Этот подход позволяет сэкономить как ресурсы сети на передачу ответа, так и ресурсы сервера на его генерацию.

Параметры формы

Раз уж речь зашла о кэшировании контента, самое время написать о том, что кэшировать не стоит — POST запросы с даными форм. HTML формы (обычно) отправляются на сервер в виде POST запроса с особым типом контента и передачей данных формы в теле запроса. Хорошая новость в том, что такие запросы обрабатываются с помощью аннотации @RequestParam:

Отправлять данные формы вручную не очень удобно: надо выставить правильный заголовок Content-Type и сформировать строку с параметрами.

Отправка файлов

А ещё из формы браузера можно отправить файл. А если запрос формируется не браузером, а скриптом или каким-то другим клиентом, то можно послать и несколько файлов и ещё добавить к ним какие-то дополнительные данные. Spring поддерживает загрузку такого контента с помощью аннотации @RequestPart:

Командой выше я отправляю json объект и какой-то первый попавшийся файл и передаю его обратно. Само содержимое файла может быть либо прочитано в память, с помощью вызова getBytes(), либо получено в виде InputStream с помощью вызова getInputStream().

Код примера доступен на github