Тарифы Услуги Сим-карты

Html использование кэша браузера. JavaScript кэширование. Как работает Cache-Control

Возникают ситуации, когда нам требуется довольно часто дергать какой-либо DOM элемент (например, с помощью querySelector). Но к сожалению, этот метод довольно прожорливый и из-за него очень сильно падает производительность. Или, например, нужно часто делать тяжелые запросы на сервер в рамках одной сессии. Либо же пользоваться функциями, которые долго что-то считают.

К счастью, мы можем воспользоваться возможностями JavaScript"a и написать функцию cache , способную кэшировать результаты работы других методов, чтобы, в случае надобности, повторно не вызывать эти самые методы:

Function cache(key, value) { if (typeof value == "undefined") { return cache; } cache = value; }

Теперь на примерах рассмотрим как будет вести себя написанная выше функция cache.

Пример 1. Кэширование querySelector // _io_q - обертка под querySelector, сохраняющая данные в кэш _io_q = function(selector) { if (!cache(selector)) { cache(selector, document.querySelector(selector)); } return cache(selector); }

Теперь посмотрим на скорость работы _io_q и querySelector:

Console.time("regular querySelector"); for (var i = 0; i < 1000000; i++) { document.querySelector("h1"); } console.timeEnd("regular querySelector"); // regular querySelector: 100.6123046875ms console.time("cached _io_q"); for (var i = 0; i < 1000000; i++) { _io_q("h1"); } console.timeEnd("cached _io_q"); // cached _io_q: 5.77392578125ms

Пример 2. Кэширование запросов с сервера

Напишем функцию, которая отправляет очень тяжелый запрос на сервер:

Function longRequestToServer(params) { var key = params.endpoint + "_" + params.value; if (!cache(key)) { var result = 0; for (var i = 0; i < 999999999; i++) { result++; } cache(key, result); } return cache(key); }

Посмотрим как будет вести себя функция:

Console.time("first run"); longRequestToServer({ endpoint: "/loadExample", value: 10}); console.timeEnd("first run"); // first run: 1012.068115234375ms console.time("second run"); longRequestToServer({ endpoint: "/loadExample", value: 10}); console.timeEnd("second run"); // second run: 1.31884765625ms console.time("other request"); longRequestToServer({ endpoint: "/loadSomeOtherData", value: 15 }); console.timeEnd("other request"); // other request: 1033.783203125ms

TL;DR

Используя возможности JavaScript"a можно эффективно кэшировать данные, выигрывая в производительности. Но будьте аккуратны, ведь выигрывая в производительности можно потерять на ресурсах и по неосторожности добиться утечки памяти.

Многие думают, что по умолчанию CSS файлы, подключаемые через link или @import - не кэшируются. Вынужден вас разочаровать. Как раз именно css, вынесенные в отдельный файл кэшируются, причем очень хорошо, я бы сказал отлично. Эта информация достоверно проверено как на 6 и выше и других браузерах. Стоит отметить, что многими любимая кэшируется такие файлы совсем с дикой скоростью, так сказать получает первое место за это дело. Кстати во многом именно этому механизму Opera имеет во многих случаях существенную скорость по сравнению с другими браузерами. Но сразу оговорюсь, что именно эта «супер» кэширование в Opera с ней злую шутки при использовании технологии AJAX. В то время как другие при использовании AJAX вносят изменения чики пуки, Opera берет старое. Но это песня отдельной темы.

Кэширование CSS

НО! Бывают все-таки у некоторых горе проблемы в этом направлении. Это связанно, как правило, с неправильным настроенным Apache сервером, который выдает не совсем корректные заголовки. А при помощи заголовком можно управлять кэшированием файлов. По умолчанию, конечно же, кэш включен всегда. Но бывают случаи, когда кэшировать файлы не нужно. Для этого и уже профи начинают танцы с бубнами по поводу HTTP заголовков. Но если вы полностью читаете эту статью, то вам еще очень далеко до управления HTTP заголовками. Уверяю вас, что в ближайшее время вы с такой задачей не столкнетесь. И все же, если вы любопытны до глубины души, то вкратце расскажу как это происходит.

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

    Сразу скажу нижний текст новичкам в WEB будет слабо понятен. В основном это полезно будет тем, кто столкнулся все-таки с задачами отключения и включения кэш.

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

    Режимы браузеров

    Итак, у любого браузера есть 2 режима :

    1. Режим по умолчанию , возвращаемый заголовок:

    Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0

    2. Режим с включенным кешированием , возвращаемый заголовок:

    Cache-Control: private, max-age=10800, pre-check=10800

    Далее описываю поведение браузеров FireFox 3.5 и выше

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

    If-Modified-Since: "текущая дата" GMT If-None-Match: "свой хэш код"

    То есть CSS загружается заново, только если он реально обновился.

    Во-втором режиме перестает обновлять страницу вообще. То есть, даже если в базе данных мы изменили выводимый на страницу контент - этого не отображает, даже если принудительно обновлять, так как отправляет запрос:

    GET / HTTP/1.1 Host: ххх.com If-Modified-Since: текущая дата GMT

    и получает ответ:

    HTTP/1.1 304 Not Modified

    Internet Explorer 8 (IE8)

    В-первом режиме Internet Explorer отправляет запросы If-Modified-Since & If-None-Match и для JavaScript и для css, то есть грузит JavaScript и CSS только если они реально обновились. То же самое если принудительно обновить страницу.

    Во-втором режиме Internet Explorer так же отправляет запросы If-Modified-Since & If-None-Match и для JavaScript и для css. Но при этом он даже не пытается загрузить/обновить саму страницу, то есть, даже не отправляет запрос, то есть, ваши js/css обновятся, а шаблон и контент страницы - нет. Для обновления контента не помогает даже принудительное обновление страницы.

    Opera 10 и старше

    В-первом режиме Опере, в первом режиме, обновление js & CSS зависит от того, в какое значение выставлена опция Check images в настройках. Если опция выставлена в значение Always, то опера отправляет запросы с If-Modified-Since & If-None-Match для проверки обновления js & css. Если выставлено значение, например, 5 часов, то соответственно проверяться будет раз в 5 часов, либо по принудительному обновлению страницы.

    Во-втором режиме, Опера не проверяет обновление js & CSS (не делает GET-запросов), а так же не делает GET запрос на саму страницу, то есть, ни обновление js & css, ни обновление контента мы не увидим, как в прочем и в других браузерах. А вот с принудительным обновлением у Оперы лучше. В отличие от IE & FF, Опера явно запрашивает содержимое страницы без If-Modified-Since & If-None-Match. Запросы на обновление js & CSS при принудительном обновлении идут уже с If-Modified-Since & If-None-Match.

    Выводы
  • Кеширование, если точно не представлять себе как оно работает в разных браузерах и какие последствия - достаточно опасная вещь.
  • Кеширование можно включать только если страница обновляется редко (то есть, если на сайте нет страниц, которые обновляются в реальном времени) и даже в этом случае обязательно нужно ставить ограничение на период ограничения кеширования (например несколько часов или день)
  • FireFox ведет себя, на мой взгляд, чуть умнее чем IE, так как даже при отключенном кешировании не проверяет постоянно обновление JavaScript, что выглядит логично, ведь JavaScript обновляется очень редко.
  • Опера позволяет гибко управлять обновлением изображений, JavaScript и CSS с помощью настройки Check images, что есть плюс. Так же Опера ведет себя лучше чем IE & FF при включенном кешировании и принудительном обновлении, так как, напомню, Опера в этом случае полностью обновляет содержимое страницы, а IE & FF - оставят вас в счастливом неведении.
  • Удачи вам и прибыльных сайтов.

    Правильно настроенное кэширование дает огромный выигрыш в производительности, экономит трафик и уменьшает затраты на сервер, однако на многих сайтах кэширование реализовано неудачно, что создает состояние гонки , приводящее к рассинхронизации взаимосвязанных ресурсов.

    Подавляющее большинство лучших практик кэширования относится к одному из двух паттернов:

    Паттерн №1: неизменяемый контент и долгий max-age кэша Cache-Control: max-age=31536000
    • Содержимое по URL не меняется, следовательно…
    • Браузер или CDN могут без проблем закэшировать ресурс на год
    • Закэшированный контент, который младше, чем заданный max-age может использоваться без консультации с сервером

    Страница: Эй, мне нужны "/script-v1.js" , "/styles-v1.css" и "/cats-v1.jpg" 10:24

    Кэш: У меня пусто, как насчет тебя, Сервер? 10:24

    Сервер: ОК, вот они. Кстати, Кэш, их стоит использовать в течение года, не больше. 10:25

    Кэш: Спс! 10:25

    Страница: Ура! 10:25

    Следующий день

    Страница: Эй, мне нужны "/script-v2 .js" , "/styles-v2 .css" и "/cats-v1.jpg" 08:14

    Кэш: Картинка с котиками есть, остального нет. Сервер? 08:14

    Сервер: Легко - вот новые CSS & JS. Еще раз, Кэш: их срок годности не больше года. 08:15

    Кэш: Супер! 08:15

    Страница: Спасибо! 08:15

    Кэш: Хм, я не пользовался "/script-v1.js" & "/styles-v1.css" достаточно долго. Пора их удалять. 12:32

    Используя этот паттерн, вы никогда не меняете контент определенного URL, вы меняете сам URL:

    В каждом URL есть что-то, меняющееся одновременно с контентом. Это может быть номер версии, модифицированная дата или хэш контента (этот вариант я и выбрал для своего блога).

    В большинстве серверных фреймворков есть инструменты, позволяющие с легкостью делать подобные вещи (в Django я использую Manifest​Static​Files​Storage); есть также совсем небольшие библиотеки в Node.js, решающие те же задачи, например, gulp-rev .

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

    Паттерн №2: изменяемый контент, всегда проходящий ревалидацию на сервере Cache-Control: no-cache
    • Содержимое URL изменится, значит…
    • Любая локальная закэшированная версия не может использоваться без указания сервера.

    Страница: Эй, мне нужно содержимое "/about/" и "/sw.js" 11:32

    Кэш: Ничем не могу помочь. Сервер? 11:32

    Сервер: Есть такие. Кэш, держи их при себе, но перед использованием спрашивай у меня. 11:33

    Кэш: Так точно! 11:33

    Страница: Спс! 11:33

    На следующий день

    Страница: Эй, мне опять нужно содержимое "/about/" и "/sw.js" 09:46

    Кэш: Минутку. Сервер, с моими копиями все в порядке? Копия "/about/" лежит с понедельника, а "/sw.js" вчерашняя. 09:46

    Сервер: "/sw.js" не менялась… 09:47

    Кэш: Круто. Страница, держи "/sw.js" . 09:47

    Сервер: …но "/about/" у меня новой версии. Кэш, держи ее, но как и в прошлый раз, не забудь сначала спросить меня. 09:47

    Кэш: Понял! 09:47

    Страница: Отлично! 09:47

    Примечание: no-cache не значит “не кэшировать”, это значит “проверять” (или ревалидировать) закэшированный ресурс у сервера. А не кэшировать совсем браузеру приказывает no-store . Также и must-revalidate означает не обязательную ревалидацию, а то, что закэшированный ресурс используется только, если он младше, чем заданный max-age , и только в ином случае он ревалидируется. Вот так все запущено с ключевыми словами для кэширования.

    В этом паттерне мы можете добавить к ответу ETag (идентификатор версии на ваш выбор) или заголовок Last-Modified . При следующем запросе содержимого со стороны клиента, выводится If-None-Match или If-Modified-Since соответственно, позволяя серверу сказать “Используй то, что у тебя есть, твой кэш актуален”, то есть вернуть HTTP 304.

    Если отправка ETag / Last-Modified невозможна, сервер всегда отсылает содержимое полностью.

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

    Это не редкость, когда у нас нет инфраструктуры для первого паттерна, но точно также могут возникнуть проблемы с сетевыми запросами в паттерне 2. В итоге используется промежуточный вариант: короткий max-age и изменяемый контент. Это плохой компромисс.

    Использование max-age с изменяемым контентом это, как правило, неправильный выбор

    И, к сожалению, он распространен, в качестве примера можно привести Github pages.

    Представьте:

    • /article/
    • /styles.css
    • /script.js

    С серверным заголовком:

    Cache-Control: must-revalidate, max-age=600

    • Содержимое URL меняется
    • Если в браузере есть кэшированная версия свежее 10 минут, она используется без консультации с сервером
    • Если такого кэша нет, используется запрос к сети, по возможности с If- Modified-Since или If-None-Match

    Страница: Эй, мне нужны "/article/" , "/script.js" и "/styles.css" 10:21

    Кэш: У меня ничего нет, как у тебя, Сервер? 10:21

    Сервер: Без проблем, вот они. Но запомни, Кэш: их можно использовать в течение ближайших 10 минут. 10:22

    Кэш: Есть! 10:22

    Страница: Спс! 10:22

    Страница: Эй, мне опять нужны "/article/" , "/script.js" и "/styles.css" 10:28

    Кэш: Упс, я извиняюсь, но я потерял "/styles.css" , но все остальное у меня есть, держи. Сервер, можешь подогнать мне "/styles.css" ? 10:28

    Сервер: Легко, он уже изменился с тех пор, как ты в прошлый раз забирал его. Ближайшие 10 минут можешь смело его использовать. 10:29

    Кэш: Без проблем. 10:29

    Страница: Спасибо! Но, кажется, что-то пошло не так! Все поломалось! Что, вообще, происходит? 10:29

    Этот паттерн имеет право на жизнь при тестировании, но ломает все в реальном проекте и его очень сложно отслеживать. В примере выше, сервер обновил HTML, CSS и JS, но выведена страница со старыми HTML и JS из кэша, к которым добавлен обновленный CSS с сервера. Несовпадение версий все портит.

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

    max-age задается относительно времени ответа, поэтому если все ресурсы передаются как часть одного адреса, их срок истечет одновременно, но и здесь сохраняется небольшой шанс рассинхронизации. Если у вас есть страницы, не включающие JavaScript или включающие другие стили, сроки годности их кэша будут рассинхронизированы. И хуже того, браузер постоянно вытаскивает содержимое из кэша, не зная, что HTML, CSS, & JS взаимозависимы, поэтому он с радостью может вытащить что-то одно из списка и забыть про все остальное. Учитывая все эти факторы вместе, вы должны понять, что вероятность появления несовпадающих версий достаточно велика.

    Для пользователя результатом может быть сломанная раскладка страницы или иные проблемы. От небольших глюков до совершенно непригодного контента.

    К счастью, у пользователей есть запасной выход…

    Обновление страницы иногда спасает

    Если страница загружена путем обновления, браузеры всегда проводят серверную ревалидацию, игнорируя max-age . Поэтому, если у пользователя что-то поломалось вследствие max-age , простое обновление страницы может все исправить. Но, разумеется, после того как ложки найдутся, осадок все равно останется и отношение к вашему сайту будет несколько иным.

    Сервис-воркер может продлить жизнь этих багов

    Например, у вас есть такой сервис-воркер:

    Const version = "2"; self.addEventListener("install", event => { event.waitUntil(caches.open(`static-${version}`) .then(cache => cache.addAll([ "/styles.css", "/script.js" ]))); }); self.addEventListener("activate", event => { // …delete old caches… }); self.addEventListener("fetch", event => { event.respondWith(caches.match(event.request) .then(response => response || fetch(event.request))); });

    Этот сервис-воркер:

    • кэширует скрипт и стили
    • использует кэш при совпадении, иначе обращается к сети

    Если мы меняем CSS/JS, мы также увеличиваем номер version , что инициирует обновление. Однако, так как addAll обращается сначала к кэшу, мы можем попасть в состояние гонки из-за max-age и несоответствующих версий CSS & JS.

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

    Вы можете пропустить кэширование в сервис-воркере:

    Self.addEventListener("install", event => { event.waitUntil(caches.open(`static-${version}`) .then(cache => cache.addAll([ new Request("/styles.css", { cache: "no-cache" }), new Request("/script.js", { cache: "no-cache" }) ]))); });

    К сожалению, опции для кэширования не поддерживаются в Chrome/Opera и только-только добавлены в ночную сборку Firefox , но вы можете сделать это самостоятельно:

    Self.addEventListener("install", event => { event.waitUntil(caches.open(`static-${version}`) .then(cache => Promise.all([ "/styles.css", "/script.js" ].map(url => { // cache-bust using a random query string return fetch(`${url}?${Math.random()}`).then(response => { // fail on 404, 500 etc if (!response.ok) throw Error("Not ok"); return cache.put(url, response); }) })))); });

    В этом примере, я сбрасываю кэш с помощью случайного числа, но вы можете пойти дальше и добавлять хэш контента при сборке (это похоже на то, что делает sw-precache). Это своего рода реализация первого паттерна с помощью JavaScript, но работающая только с сервис-воркером, а не браузерами и CDN.

    Сервис-воркеры и HTTP-кэш отлично работают вместе, не заставляйте их воевать!

    Как видите, вы можете обойти ошибки с кэшированием в вашем сервис-воркере, но правильней будет решить корень проблемы. Правильная настройка кэширования не только облегчает работу сервис-воркера, но и помогает браузерам, не поддерживающим сервис-воркеры (Safari, IE/Edge), а также позволяет вам извлечь максимум из вашей CDN.

    Правильные заголовки кэширования также могут значительно упростить обновление сервис-воркера.

    Const version = "23"; self.addEventListener("install", event => { event.waitUntil(caches.open(`static-${version}`) .then(cache => cache.addAll([ "/", "/script-f93bca2c.js", "/styles-a837cb1e.css", "/cats-0e9a2ef4.jpg" ]))); });

    Здесь я закэшировал корневую страницу с паттерном №2 (серверная ревалидация) и все остальные ресурсы с паттерном №1 (неизменяемый контент). Каждое обновление сервис-воркера будет вызывать запрос к корневой странице, а все остальные ресурсы будут загружаться только, если их URL изменился. Это хорошо тем, что сохраняет трафик и улучшает производительность, независимо от того, обновляетесь ли вы с предыдущей или очень старой версии.

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

    Сервис-воркеры работают лучше в качестве улучшения, а не временного костыля, поэтому работайте с кэшем вместо того, чтобы воевать с ним.

    При аккуратном использовании max-age и изменяемый контент могут быть очень хороши

    max-age очень часто бывает неправильным выбором для изменяемого контента, но не всегда. Например, у оригинала статьи max-age составляет три минуты. Состояние гонки не является проблемой, так как на странице нет зависимостей, использующих одинаковый паттерн кэширования (CSS, JS & изображения используют паттерн №1 - неизменяемый контент), все остальное этот паттерн не использует.

    Этот паттерн означает, что я спокойно пишу популярную статью, а мой CDN (Cloudflare) может снять нагрузку с сервера, если, конечно, я готов подождать три минуты, пока обновленная статья станет доступной пользователям.

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

    При правильном использовании кэширование дает значительное улучшение производительности и экономию трафика. Передавайте неизменяемый контент, если вы можете легко изменить URL, или используйте серверную ревалидацию. Смешивайте max-age и изменяемый контент, если вы достаточно смелы и уверены, что у вашего контента нет зависимостей, которые могут рассинхронизироваться.

    Порой, бывает необходимо запрещать браузеру кэшировать страницу, так как информация на ней обновляется каждый раз. Это может быть генерация данных, соответственно выбранным фильтрам или другой контент, который каждый раз создается по-новому. Одним словом, бывают моменты, когда необходимо запретить коварной программе кэшировать страницу. Сегодня, мы узнаем, как реализовать это разными способами, с помощью PHP или HTML или.htaccess.

    Запрет кэширования страницы на HTML

    Сделать это можно с помощью мета тегов. Сейчас мы разберем разные варианты запрета на кэширование.

    Запрет на кэширование браузером и прокси-сервером

    Запрет кэширования страницы, только браузером

    Установка кэширования на определенное время, для браузера

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

    Установка кэширования на определенное время, для прокси-сервера

    Практически, то же самое, что и в предыдущем коде, только указание стоит конкретно для прокси-сервера.

    Запретить кэширование страницы с помощью PHP

    Практически, все тоже самое, что в случае с HTML, только информацию будем выводить через header заголовки. Вот, как реализовать абсолютный запрет на кэш:

    Также, можно разрешать кэшировать на определенное время. Например, разрешим кэширование только на 1 час.

    Запретить кэширование страницы с помощью.htaccess

    Для простоты реализации идеи, можно все сделать на уровне конфигураций сервера Apache. Перед этим, нам нужно убедиться в том, что необходимые модули находятся в рабочем состоянии. Открываем конфигурационный файл Apache и наблюдаем следующую картину:

    LoadModule expires_module modules/mod_expires.so LoadModule headers_module modules/mod_headers.so ... AddModule mod_expires.c AddModule mod_headers.c

    Теперь в файле.htaccess, собственно запрещаем кэшировать выводимые данные. Как нам известно, .htaccess файл будет распространяться на директорию, в которой лежит, и на все субдиректории.

    # Заголовок Cache-Control Header append Cache-Control "no-store, no-cache, must-revalidate" # Заголовок Expires ExpiresActive On ExpiresDefault "now"

    Важно заметить, что полный запрет кэширования, повышает нагрузку на сервер. Поэтому, играйтесь с этим осторожно! А лучше, установите определенное время, на которое можно кэшировать документы. Например, установим кэширование на 1 час:

    # Заголовок Cache-Control Header append Cache-Control "public" # Заголовок Expires ExpiresActive On ExpiresDefault "access plus 1 hours"

    Заключение

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

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

    Но для начала давайте выясним, зачем вообще нужно кэширование на стороне клиента? .

    Веб-страницы состоят из множества различных элементов: картинок, css и js файлов и т.п. Часть этих элементов используются на нескольких (многих) страницах сайта. Под клиентским кэшированием понимают способность браузеров сохранять копии файлов (ответов сервера), чтобы не загружать их повторно. Это позволяет значительно ускорить повторную загрузку страниц, сэкономить на трафике, а также снизить нагрузку на сервер.

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

    Http заголовки для управления клиентским кэшированием

    Для начала давайте посмотрим, как сервер и браузер взаимодействуют при отсутствии какого-либо кэширования. Для наглядного понимания я попытался представить и визуализировать процесс общения между ними в виде текстового чата. Представьте на несколько минут, что сервер и браузер – это люди, которые переписываются друг с другом:)

    Без кэша (при отсутствии кэширующих http-заголовков)

    Как мы видим, каждый раз при отображении картинки cat.png браузер будет снова загружать ее с сервера. Думаю, не нужно объяснять, что это медленно и неэффективно.

    Заголовок ответа Last-modified и заголовок запроса if-Modified-Since .

    Идея заключается в том, что сервер добавляет заголовок Last-modified к файлу (ответу), который он отдает браузеру.

    Теперь браузер знает, что файл был создан (или изменен) 1 декабря 2014. В следующий раз, когда браузеру понадобится тот же файл, он отправит запрос с заголовком if-Modified-Since .

    Если файл не изменялся, сервер отправляет браузеру пустой ответ со статусом 304 (Not Modified) . В этом случае, браузер знает, что файл не обновлялся и может отобразить копию, которую он сохранил в прошлый раз.

    Таким образом, используя Last-modified мы экономим на загрузке большого файла, отделываясь пустым быстрым ответом от сервера.

    Заголовок ответа Etag и заголовок запроса If-None-Match .

    Принцип работы Etag очень схож с Last-modified , но, в отличии от него, не привязан ко времени. Время – вещь относительная.

    Идея заключается в том, что при создании и каждом изменении сервер помечает файл особой меткой, называемой ETag , а также добавляет заголовок к файлу (ответу), который он отдает браузеру:

    ETag: "686897696a7c876b7e"

    Теперь браузер знает, что файл актуальной версии имеет ETag равный “686897696a7c876b7e”. В следующий раз, когда брузеру понадобится тот же файл, он отправит запрос с заголовком If-None-Match: "686897696a7c876b7e" .

    If-None-Match: "686897696a7c876b7e"

    Сервер может сравнить метки и, в случае, если файл не изменялся, отправить браузеру пустой ответ со статусом 304 (Not Modified) . Как и в случае с Last-modified браузер выяснит, что файл не обновлялся и сможет отобразить копию из кэша.

    Заголовок Expired

    Принцип работы этого заголовка отличается от вышеописанных Etag и Last-modified . При помощи Expired определяется “срок годности” (“срок акуальности”) файла. Т.е. при первой загрузке сервер дает браузеру знать, что он не планирует изменять файл до наступления даты, указанной в Expired:

    В следующий раз браузер, зная, что “дата истечения срока годности” еще не наступила, даже не будет пытаться делать запрос к серверу и отобразит файл из кэша.

    Такой вид кэша особенно актуален для иллюстраций к статьям, иконкам, фавиконкам, некоторых css и js файлов и тп.

    Заголовок Cache-control с директивой max-age .

    Принцип работы Cache-control: max-age очень схож с Expired . Здесь тоже определяется “срок годности” файла, но он задается в секундах и не привязан к конкретному времени, что намного удобнее в большинстве случаев.

    Для справки:

    • 1 день = 86400 секунд
    • 1 неделя = 604800 секунд
    • 1 месяц = 2629000 секунд
    • 1 год = 31536000 секунд

    К примеру:

    Cache-Control: max-age=2629000;

    У заголовка Cache-control , кроме max-age , есть и другие директивы. Давайте коротко рассмотрим наиболее популярные:

    public
    Дело в том, что кэшировать запросы может не только конечный клиент пользователя (браузер), но и различные промежуточные прокси, CDN-сети и тп. Так вот, директива public позволяет абсолютно любым прокси-серверам осуществлять кэширование наравне с браузером.

    private
    Директива говорит о том, что данный файл (ответ сервера) является специфическим для конечного пользователя и не должен кэшироваться различными промежуточными прокси. При этом она разрешает кэширование конечному клиенту (браузеру пользователя). К примеру, это актуально для внутренних страниц профиля пользователя, запросов внутри сессии и т.п.

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

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

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

    proxy-revalidate
    Это то же, что и must-revalidate , но касается только кэширующих прокси серверов.

    s-maxage
    Практически не отличается от мах-age , за исключением того, что эта директива учитывается только кэшем резличных прокси, но не самим браузером пользователя. Буква “s -” исходит из слова “s hared” (например, CDN). Эта директива предназначена специально для CDN-ов и других посреднических кэшей. Ее указание отменяет значения директивы max-age и заголовка Expired . Впрочем, если вы не строите CDN-сети, то s-maxage вам вряд ли когда-либо понадобится.

    Как посмотреть, какие заголовки используются на сайте?

    Вы можете посмотреть заголовки http-запросов (request headers) и ответов (response headers) в отладчике Вашего любимого браузера. Вот например, как это выглядит в хроме:

    То-же самое можно увидеть в любом уважающем себя браузере или http-сниффере.

    Настройка кэшировения в Аpache и Nginx

    Мы не будем пересказывать документацию по настройке популярных серверов. Вы всегда можете посмотреть ее и . Ниже мы приведем несколько примеров из жизни для того, чтобы показать, как выглядят файлы конфигурации.

    Пример конфигурации Apache для контроля Expires

    Выставляем различный “срок годности” для различных типов файлов. Один год для изображений, один месяц для скриптов, стилей, pdf и иконок. Для всего остального – 2 дня.

    ExpiresActive On ExpiresByType image/jpg "access plus 1 year" ExpiresByType image/jpeg "access plus 1 year" ExpiresByType image/gif "access plus 1 year" ExpiresByType image/png "access plus 1 year" ExpiresByType text/css "access plus 1 month" ExpiresByType application/pdf "access plus 1 month" ExpiresByType text/x-javascript "access plus 1 month" ExpiresByType image/x-icon "access plus 1 year" ExpiresDefault "access plus 2 days"

    Пример конфигурации Nginx для контроля Expires

    Выставляем различный “срок годности” для различных типов файлов. Одна неделя – для изображений, один день – для стилей и скриптов.

    Server { #... location ~* \.(gif|ico|jpe?g|png)(\?+)?$ { expires 1w; } location ~* \.(css|js)$ { expires 1d; } #... }

    Пример конфигурации Apache для Cache-control (max-age и public/private/no-cache) Header set Cache-Control "max-age=2592000, public" Header set Cache-Control "max-age=88000, private, must-revalidate" Header set Cache-Control "private, no-store, no-cache, must-revalidate, no-transform, max-age=0" Header set Pragma "no-cache" Пример конфигурации Nginx для Cache-control статических файлов server { #... location ~* \.(?:ico|css|js|gif|jpe?g|png)$ { add_header Cache-Control "max-age=88000, public"; } #... } В заключение

    “Кэшировать все то, что можно кэшировать” – хороший девиз для веб-разработчика. Иногда можно потратить всего несколько часов на конфигурацию и при этом значительно улучшить восприятие вашего сайта пользователем, значительно сократить нагрузку на сервер и сэкономить на трафике. Главное – не переусердствовать и настроить все правильно с учетом особенностей Вашего ресурса.