Или как правильно отдавать статику

О чём это?
Одним из самых значимых, на мой взгляд, плюсов реализации проекта в виде веб-приложения является простота обновления рабочей версии. Которая конечно же вытекает из клиент-серверной архитектуры. Чтобы что-то исправить вам надо просто обновить ваш сервер(а), и при следующей загрузке страницы пользователь увидит новую версию сайта.
Однако, иногда в этот процесс вмешивается кеш браузера клиента. И, если не подчинить себе этот инструмент, то он станет вашим врагом! Давайте разберёмся, что нужно сделать чтобы взять его в союзники..
Мат. часть
Под статичными файлами я, конечно-же, имею ввиду картинки, скрипты, шрифты, таблицы стилей и т.д.. Это содержимое сайта, которое не меняется в пределах одной версии проекта. В отличии от, например, html-страниц которые обычно создаются при каждом запросе клиента или от AJAX-запросов.
Сохранением данных в браузере клиента управляет HTTP-заголовок сервера Expires. В нём указывается время после которого данные необходимо считать устаревшими. Например
Expires: Thu, 15 Sep 2016 13:10:01 GMT
Недействительно после 15 сентября 2016 года 13 часов 10 минут и одной секунды. При настройке сервера это указывается в конфиге сервера как
Apache2
ExpiresDefault "access plus 10 days"
Nginx
expires 10d;
Только не спешите уменьшать это значение до предела, если Ваши клиенты начнут жаловаться, что новые возможности не появляются на вашем сайте! Давайте сначала разберёмся..
Почему кеш наш друг?
- Ускорение загрузки сайтаКлиент не будет ждать получения статичных данных при загрузке второй и последующих страниц, они уже будут в его браузере
- Уменьшение трафика на клиентеОпять же, клиент меньше потратит трафика чтобы загрузить сайт. А это очень актуально для мобильных платформ!
- Уменьшение нагрузки на серверСервер меньше занимается чтением файлов, и больше полезной нагрузкой. Ну а если проект в облаке то это уменьшает и стоимость аренды мощностей.
Что идёт не так?
По сути, чтобы пользоваться кешем надо уметь сказать браузеру о том что версия файла изменилась. К примеру запись:
<link rel="stylesheet" href="/static/style.css">
Надо модифицировать через GET-параметр
<link rel="stylesheet" href="/static/style.css?v=1.15">
Либо сохранить версию в имени или пути файла
<link rel="stylesheet" href="/static/style.1.15.css">
Иначе клиенты будут считать что файл не изменился, и будут брать файл из кеша пока не превышен Expires. Вот, например, что пишет про это Yahoo в хорошей статье про оптимизацию веб-приложений
Решение
В общем-то сделать всё можно по-разному. Тут я рассмотрю встроенное в Django решение, которое я, почему-то, очень долго не видел в этом фреймворке.
Решение очень хорошо описано в документации и состоит в использовании приложения staticfiles для выставления ссылок в темплейтах, и дополнительной настройки STATICFILES_STORAGE в ManifestStaticFilesStorage или в CachedStaticFilesStorage:
# settings.py
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
# если серверный кеш настроен
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.CachedStaticFilesStorage'
Как работает
При запуске ./manage.py collectstatic ко всем статичным файлам добавится дополнительный суффикс вычисляемый динамически (md5 по контенту).
Дополнительно, создаётся файл манифеста staticfiles.json (запись в серверном кеше для CachedStaticFilesStorage), который содержит словарь соответствий оригинально имени файла к его весифицированной копии:
"/img/logo_new.png": "/img/logo_new.02d565a9c2db.png"
При создании html-страницы, если DEBUG=False, в темплейте происходит выставление имён на основе staticfiles.json. Таким образом, в момент генерации страницы сложная операция по вычислению хеша файлов не выполняется.
В продакшн!
Вот в общем-то и всё. Хочется ещё раз подчеркнуть, что решение работает "из коробки"! В лучшем случае, вам придётся заменить в темплейтах {% load static %} на {% load staticfiles %}. В худшем - убрать устаревшее {{ STATIC_URL }} или и вовсе не правильноe прямое указание пути href="/static/my_file.css", хотя это так и так лучше бы сделать..
Спасибо за внимание