пятница, Август 11, 2006

Кэширование и сжатие: теория

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

Когда клиент скачивает по протоколу HTTP какую-либо полезную информацию, он сохраняет её в памяти и затем отображает пользователю. При разработке стандарта HTTP Тим Бернерс Ли предложил способ сократить количество передаваемых данных за счёт системы кэширования, целью которой было предотвращение повторной загрузки ресурса, который уже был загружен клиентом и не изменился.

Основа кэширования

Кэширование всегда инициируется сервером, и поддерживается клиентом. Самый простой способ кэширования выглядит так:
Клиент запрашивает какой-то ресурс, сервер включает в заголовки ответа заголовок Expires со значением какой-либо даты. Этот заголовок «говорит» клиенту о том, что данный ресурс будет актуальным до даты, указанной в нём. Если указать некоторую дату в будущем, клиент будет вправе до этой даты при запросе пользователя выдавать ресурс из своего кэша. Если указать 0, текущую дату или дату в прошлом, клиент будет при каждом запросе заново скачивать весь ресурс, так как будет полагать, что «срок годности» ресурса истёк и следует получить его свежую версию.

Механизм метода устаревания документа

  • клиент запрашивает какой-либо ресурс.
    GET http://somehost.ru/index.html HTTP/1.0
    
  • Сервер, «знающий», что данный ресурс не устареет до определённой даты, включает её в заголовок Expires.
    HTTP/1.0 200 OK
    Date: Tue, 08 Aug 2006 08:15:21 GMT
    Expires: Wed, 09 Aug 2006 08:15:21 GMT
    Content-Length: 4
    Content-Type: text/html
    
    test
    
    В данном примере ресурс index.html устареет ровно через день.
  • Клиент высчитывает «срок годности» ресурса (просто вычитая Date из Expires) и сохраняет в кэше ресурс вместе со сроком годности.
  • В течение этого «срока годности» клиент не будет запрашивать ресурс с сервера вовсе, а будет отдавать его пользователю из кэша.

В стандарте HTTP/1.1 был добавлен заголовок Cache-control, контролирующий механизм кэширования (о нём я расскажу ниже). Одним из его возможных атрибутов является атрибут max-age, явно указывающий срок годности ресурса. В случае, если клиент поддерживает HTTP/1.1 и видит в заголовке Cache-control, принятом от сервера, атрибут max-age с каким-либо значением, клиент принимает срок годности, указанный в max-age, даже если есть заголок Expires с другим значением. Другими словами, max-age имеет бóльший приоритет, чем Expires, что позволяет, например, предоставлять разные возможности кэширования HTTP/1.0 и HTTP/1.1-клиентам.

Механизм устаревания ресурса удобен в некоторых случаях. Например, очень удобно отдавать различные служебные изображения на сайте с заведомо большим сроком устаревания, например, размером в год. Таким образом можно предотвратить большинство самых частых запросов — клиенты будут скачивать такие изображения очень редко. Недостаток один — если картинка изменилась, нужно как-то заставить клиента заново её скачать. Обычно изображение просто переименовывают, клиент, соответственно, думает, что это новое изображение и скачивает его. Также, если сайт устоялся и на нём не планируется в ближайшее время изменений, можно выдать таблицы стилей и скрипты с таким заголовком.
Необходимо заметить, что большинство серверов для статических файлов автоматически реализуют кэширование, основанное на методе «GET-запроса с условием» (см. ниже), что даёт экономию в трафике, но всё равно заставляет клиента каждый раз выполнять множество запросов, что, в свою очередь, вынуждает сервер проверять все условия и отвечать клиенту либо 304 Not Modified, либо отдавать сам ресурс. Наиболее оптимально совмещать эти два способа, потому я и предлагаю заняться кэшированием вручную.

Резюмируем метод кэширования, основанного на устаревании ресурса:

  • В течение срока, установленного сервером в заголовке Expires (или в значении атрибута max-age заголовка Cache-control), клиент отображает пользователю ресурс из кэша, вообще не запрашивая сервер.
  • Метод хорош для служебных данных — изображений, таблиц стилей, скриптов, — для действительно редко изменяемых данных.
  • Наиболее оптимально сочетать этот метод с методом «GET-запроса с условием».

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

«GET-запрос с условием» (conditional GET)

Не всегда авторам известна дата устаревания ресурса, потому был создан механизм «GET-запроса с условием». Общий механизм прост — сервер каким-либо образом идентифицирует ресурс, и клиент в последующем запросе добавляет в заголовки присланный сервером идентификатор; сервер сверяет собственный и присланный идентификатор, если они равны, значит, ресурс не изменился, в таком случае сервер говорит клиенту, чтобы тот показывал ресурс из кэша. Если же идентификаторы различаются, сервер заново отдаёт ресурс с новым идентификатором. Образно говоря, клиент просит сервер отдать документ, если он изменился.

«GET-запрос с условием» в HTTP/1.0

При запросе клиентом ресурса, определенного URI, сервер может добавить к стандартным заголовкам ответа заголовок Last-Modified со значением даты последней модификации ресурса. В случае запроса статического файла в качестве времени модификации удобно использовать время его последней модификации в файловой системе.
Клиент получает ответ, и если он умеет работать с кэшем, сохраняет скачанный документ вместе с полученной датой. При следующем запросе ресурса с тем же URI клиент смотрит в свой кэш, и если находит ресурс с сохранённой датой(полученной из заголовка Last-Modified), добавляет к заголовкам запроса заголовок If-Modified-Since, куда подставляет сохранённую дату.
Сервер, получая такой заголовок, сравнивает дату модификации ресурса у себя с датой, полученной в заголовке If-Modified-Since. Если даты совпали (или дата, присланная клиентом, «больше»), сервер отдаёт клиенту заголовок 304 Not Modified, таким образом оповещая клиента о том, что ресурс не изменился и клиент может отобразить его из своего кэша. Если у клиента дата старее, чем дата модификации ресурса, сервер отдаёт клиенту ресурс заново вместе с новой датой модификации.

Механизм работы «GET-запроса с условием» HTTP/1.0:

  1. клиент запрашивает какой-либо ресурс.
    GET http://somehost.ru/index.html HTTP/1.0
    
  2. Сервер, поддерживающий кэширование для данного ресурса, добавляет в ответ заголовок Last-Modified.
    HTTP/1.0 200 OK
    Date: Fri, 30 Dec 2003 13:20:12 GMT
    Last-Modified: Fri, 29 Dec 2003 13:20:12 GMT
    Content-Length: 4
    Content-Type: text/html
    
    test
    
  3. Клиент сохраняет в кэше документ вместе с датой, полученной от сервера в заголовке Last-Modified.
  4. В следующий раз клиент при запросе того же ресурса в заголовок запроса добавит сохранённую дату
    GET http://somehost.ru/index.html HTTP/1.0
    If-Modified-Since: Fri, 29 Dec 2003 13:20:12 GMT
    
  5. Сервер сравнит полученную дату с датой последнего изменения ресурса. Если на сервере находится более «свежая» версия ресурса, он отдаст её вместе с новой датой модификации.
    HTTP/1.0 200 OK
    Date: Sat, 31 Dec 2003 11:20:22 GMT
    Last-Modified: Sat, 31 Dec 2003 10:00:00 GMT
    Content-Length: 7
    Content-Type: text/html
    
    newtest
    
    Если же на сервере документ актуален (т.е. даты равны), то сервер отдаст заголовок HTTP/1.0 304 Not Modified, но уже не отдаст тело ответа
    HTTP/1.0 304 Not Modified
    Date: Sat, 31 Dec 2003 11:20:22 GMT
    
    Клиент же, получив такой ответ, покажет пользователю документ из своего кэша.

Дата, указанная в Last-Modified, должна быть именно в таком виде и в формате GMT. Строго говоря, и HTTP/1.0 и HTTP/1.1 обязывают серверы понимать три формата времени:

Sun, 06 Nov 1994 08:49:37 GMT 
Sunday, 06-Nov-94 08:49:37 GMT 
Sun Nov  6 08:49:37 1994       
Но HTTP/1.0 добавляет, что сами серверы не имеют права использовать последний формат, а HTTP/1.1 требует от серверов использовать только первый формат времени.

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

Резюмируем:

  • метод применим для любых типов ресурсов, если для них возможно определить время модификации
  • метод поддерживается всеми клиентами и всеми прокси-серверами (уж HTTP/1.0 сейчас реализуют все)
  • в сочетании с методом кэширования, основанном на устаревании ресурса (Expires) идеален для кэширования служебных изображений, таблиц стилей, скриптов. Клиент не будет запрашивать сервер до даты, определённой в Expires, а затем, когда запросит, если ресурс не изменился, получит 304 Not Modified с новым значением Expires и снова перестанет запрашивать сервер до даты из нового Expires.

«GET-запрос с условием» в HTTP/1.1

В HTTP/1.1 в механизм «GET-запроса с условием» было добавлено ещё одно «условие» т.н. ETag — сущность, однозначно определяющая содержимое ресурса. Это может быть хэш всего ресурса, его контрольная сумма или другая функция, однозначно идентифицирующая содержимое. В ситуации, когда документ генерируется шаблонной системой и CMS, бывает сложно определить время модификации ресурса, особенно если он «собирается» из нескольких частей. Вот в таких случаях удобнее определить ETag и сравнить его с присланным клиентом. Механизм сравнения похож на реализованный в HTTP/1.0 механизм сравнения даты модификации. Только если в HTTP/1.0 использовалась пара заголовков Last-Modified(сервер)/If-Modified-Since(клиент), то в в HTTP/1.1 используется пара ETag(сервер)/If-None-Match(клиент), использующаяся таким же образом — сервер идентифицирует ресурс и шлёт клиенту в заголовке ETag этот идентификатор. Клиент в следующий раз в запрос того же ресурса добавляет заголовок If-None-Match с сохраненным значением присланного сервером заголовка ETag. Сервер сверяет идентификаторы и либо возвращает 304 Not Modified в случае, если ресурс не изменился, или же возвращает заново весь ресурс с новым значением ETag.

Механизм работы «GET-запроса с условием» HTTP/1.1:

  1. клиент запрашивает какой-либо ресурс.
    GET /index.html HTTP/1.1
    Host: somehost.ru
    
  2. Сервер, поддерживающий кэширование для данного ресурса, добавляет в ответ заголовок ETag.
    HTTP/1.1 200 OK
    Date: Fri, 30 Dec 2003 13:20:12 GMT
    ETag: "328e-1d-1b63fda6"
    Content-Length: 4
    Content-Type: text/html
    
    test
    
  3. Клиент сохраняет в кэше документ вместе со значением заголовка ETag, полученным с сервера.
  4. В следующий раз клиент при запросе того же ресурса в заголовок запроса добавит сохранённое значение ETag.
    GET /index.html HTTP/1.1
    Host: somehost.ru
    If-None-Match: "328e-1d-1b63fda6"
    
  5. Сервер сравнит полученное значение идентификатора ETag с собственным. Если идентификаторы различаются, сервер отдаст документ вместе с новым значением идентификатора ETag.
    HTTP/1.1 200 OK
    Date: Sat, 31 Dec 2003 11:20:22 GMT
    ETag: "435b-4t-jla890l9"
    Content-Length: 7
    Content-Type: text/html
    
    newtest
    
    Если же значение идентификатора ETag, присланное клиентом совпадает со значением серверным, то сервер отдаст заголовок HTTP/1.1 304 Not Modified, но уже не отдаст тело ответа.
    HTTP/1.1 304 Not Modified
    Date: Sat, 31 Dec 2003 11:20:22 GMT
    
    Клиент же, получив такой ответ, покажет пользователю документ из своего кэша.

Резюмируем:

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

Сжатие

Сжатие ответа было представлено в HTTP/1.0. Оно выполняется просто. Если клиент поддерживает какой-то механизм кодирования, он к каждому запросу может приложить заголовок Accept-Encoding с перечислением поддерживающихся механизмов кодирования. Сервер может закодировать ресурс одним из методов, поддерживающихся клиентом. В HTTP/1.0 были введены два типа кодирования — x-gzip и x-compress (но было сказано, что gzip и compress им эквивалентны для совместимости с будущими реализациями протокола). В HTTP/1.1 уже просто используются gzip и compress (и говорится, что программы должны понимать x-gzip и x-compress для совместимости с предыдущими реализациями протокола:)). Также в HTTP/1.1 был введён метод deflate, но из-за кучи проблем с реализацией в разных браузерах этот метод используется гораздо реже. Чаще всего используется gzip. Его поддерживают все браузеры, начиная c IE4, Opera5.12, NN4.06 и ранних версий Firefox. Сжимать можно любой тип данных, но большинство браузеров понимает только text/plain, text/html, text/css, text/javascript. Необходимо заметить, что при сжатии в заголовке Content-Length указывается длина сжатого тела сообщения.

Последовательность работы механизма сжатия:

  • Клиент запрашивает какой-то ресурс, сообщая серверу о поддерживаемых типах кодирования:
    GET /index.html HTTP/1.1
    Host: somehost.ru
    Accept-Encoding: gzip,deflate
  • Сервер выбирает тип кодирования, кодирует ресурс и шлёт пользователю
    HTTP/1.1 200 OK
    Date: Thu, 10 Aug 2006 17:08:26 GMT
    Content-Encoding: gzip
    Content-Length: 59
    
    всякая_пожатая_непроизносимая_ерунда
    
  • Клиент, зная тип кодирования, декодирует ресурс.

Необходимо осветить также заголовок вариантности кэширования.

Заголовок вариантности Vary

Vary: Accept-Encoding нужен для того, чтобы дать понять кэширующему механизму, что ответ сервера зависит от принимаемого клиентом метода кодирования. Предположим ситуацию: есть два клиента, один поддерживает gzip, другой нет. Поддерживающий gzip клиент шлёт в запросе Accept-Encoding: gzip, не поддерживающий не шлёт, соответственно :) Всё замечательно работает — сервер шлёт одному клиенту пожатый контент, другому несжатый. Но если на пути к серверу попадётся прокси-сервер, поддерживающий кэширование, возникает проблема. Если первый клиент, поддерживающий gzip, получает от сервера пожатый контент, прокси эти данные кэширует. При обращении к тому же ресурсу второго клиента(не поддерживающего сжатие), прокси-сервер отдаёт этому клиенту пожатый контент! Клиент ничего не понимает :) Эта проблема решается с помощью посылаемого сервером заголовка Vary. Его значением может быть любое HTTP-поле. Фактически этот заголовок говорит, что ответ на запрос будет разным в зависимости от некоторых заголовков. В результате в кэше будут храниться все варианты ответов. Если скрипт отдаёт разные данные в зависимости от Cookie, нужно отдавать Vary: Cookie, таким образом кэш отдаст закэшированный вариант ответа только для конкретной куки. Например, если два клиента обращались к ресурсу с Cookie: name=Vasya и Cookie: name=Petya, будет закэшированы отдельно ответы для обоих клиентов, соответственно Васе будет отдан его ответ, Пете — его. Ещё раз акцентирую внимание на том, что все условия, относительно которых может быть получен разный ответ сервера, нужно прописывать в Vary, чтобы не допустить попадания кэша одного пользователя другому.

Нужно сказать, что существуют серьёзные проблемы при использовании сжатия. Во-первых, это нагрузка на процессор сервера. Ни в коем случае не нужно на лету сжимать часто скачиваемые ресурсы. В таком случае правильнее будет сжимать их сразу при изменении, и потом отдавать уже сжатые данные. Сжатие оптимально использовать совместно со стратегиями кэширования (см. ниже). Также существует несколько проблем в реализации сжатия некоторыми браузерами.

Проблемы со сжатием в различных браузерах:

  • IE5.5 и IE6 забывает об ETag при использовании заголовка вариантности кэширования Vary и просто при использовании кодирования gzip. В IE7 этой проблемы нет.
  • IE4.0 и IE5.0 вообще не умеют кэшировать сжатое содержимое.
  • Opera не умеет кэшировать сжатый text/css, text/javascript
  • IE5.5 и IE6 без сервис-пака из-за бага могут терять первые 2 килобайта сжатых данных (редкий случай, я не встречал).

Лучше всего себя в работе с кэшированием и сжатием показали Firefox и IE7b3.

Стратегии кэширования

Кэширование служебных изображений

  • Служебные изображения не нужно сжимать, это лишняя и бесполезная нагрузка на процессор, т.к. чаще всего такие изображения уже максимально сжаты в графическом редакторе.
  • Кэширование изображений нужно реализовывать на основе Expires, ETag, Last-Modified. Expires предлагает клиенту брать ответ на запрос из кэша в течение всего срока, Last-Modified и ETag обеспечивают «GET-запрос с условием», Last-Modified обеспечивает поддержку HTTP/1.0-клиентов, ETag — HTTP/1.1. Таким образом, если клиент и приходит за изображением, то происходит «GET-запрос с условием» и клиент получает изображение только если оно изменилось.
  • Принудительно заставить всех клиентов получить изображение заново, несмотря на срок «годности» Expires можно только изменив путь к изображению или переименовав его.

Кэширование таблиц стилей и скриптов

Если сайт «устоялся» и не планируются частые изменения, то стратегия кэширования идентична кэшированию изображений, за исключением того, что к таблицам стилей и скриптам можно также применить сжатие. Правда, нужно решить, что для Вас важнее — неудобство пользователей Opera, теряющих из-за сжатия возможность кэширования или же удобство пользователей остальных браузеров, умеющих кэшировать сжатые таблицы стилей и скрипты. В принципе, можно совместить требования. Получается следующее:

  • Добавляем в заголовок вариантности Vary вариант User-Agent для того, чтобы показать кэшу, что разным браузерам будет отдано разное содержимое.
  • Opera до 9-й версии включительно и ie4/5.0 нужно отдавать несжатое содержимое. Только так эти браузеры смогут кэшировать содержимое.
  • Для IE6 необходимо проверять только If-Modified-Since.

Кэширование (x)html-документов

Нужно определиться, необходимо ли Вам вообще кэширование (x)html-документов. Если клиент отобразит пользователю документ из кэша, Вы не получите «показа» баннера рекламной сети, и что ещё очевиднее, не сможете учесть посетителя на Ваш сайт — ведь по сути пользователь его не посетил :) Если же Вы цените удобство Ваших пользователей больше, чем рекламные показы, кэшируйте и (x)html. Стратегия кэширования похожа на используемую для таблиц стилей и скриптов, правда, с несколькими «но».

UPDATE:

как абсолютно правильно поправил меня anonymous в комментах, неизменность (x)html-документа совершенно не гарантирует неизменности картинки с баннерной сети, в любом работа с этой картинкой будет зависеть только от заголовкой, с которыми она была отдана баннерной сетью.
Если Вы проверяете заголовки вручную в скрипте и хотите учитывать посещения на сайт, можно просто считать обращения к этому скрипту, в любом случае даже при conditional GET скрипт будет запущен для проверки. Единственное, от чего придётся отказаться — от Expires, т.к. в течение срока устаревания, установленного в Expires клиент не будет посещать Ваш сайт вообще. Если же подсчёт посещаемости для Вас не важен и есть возможность устанавливать Expires, устанавливайте его :)

Иногда (x)html-код генерируется автоматически, потому использование времени последней модификации невозможно. Остаётся ETag. Cледовательно, учитывая неумение IE6 кэшировать сжатый контент на основе ETag, от сжатия, скорее всего, придётся отказаться; или же слать сжатое содержимое только тем браузерам, которые не забывают про Etag. Если же (x)html-код находится в статических файлах, можно и для IE6 использовать сжатие совместно с кэшированием на основе Last-Modified. Также, как и в случае с кэшированием таблиц стилей и скриптов, статические файлы можно заранее сжать, чтобы потом не тратить ресурсы на сжатие на лету.

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

8 Comments:

Anonymous Анонимный said...

***start quote***
Если клиент отобразит пользователю документ из кэша, Вы не получите «показа» баннера рекламной сети, и что ещё очевиднее, не сможете учесть посетителя на Ваш сайт — ведь по сути пользователь его не посетил :)
***end quote***

это ещё почему?
разве не изменность xhtml говорит о том что картинки, ифрейми и прочее тоже не изменились?

а поповоду посещений, по сути если мы не юзаем last-modified то в любом случае скрипт выполняеца и мы можем записать посещение.

11 Август, 2006 02:27 
Blogger Bitari Shirobatobu said...

упс, конечно :) Спасибо большое, протупил :)

По поводу же Last-Modified: скрипт не выполнится только если мы будем использовать Expires, и клиент не будет приходить вовсе. Использование же Last-Modified подразумевает conditional get, стало быть, если мы отдаём и сверяем заголовки вручную в скрипте, скрипт будет запущен каждый раз, когда будет происходить этот самый conditional get.

11 Август, 2006 08:24 
Anonymous Анонимный said...

[color=#58a]Музыканты на праздник [url=http://dejavu-group.ru/artist.php]Дежа вю[/url] - это коллектив дипломированных вокалистов и музыкантов на праздник.
[url=http://dejavu-group.ru/index.php]Дежа вю[/url]- законодатель в области проведения и организации праздников, дней рождения, шоу-программ, копроративов, торжеств и дней рождения.
В копилке музыкантов на праздник Deja Vu более 3000 произведений.
Живой звук. Ретро, хиты 70-80-90-х, джаз, диско, поп, современная музыка, европейские хиты, фоновая музыка, шансон.
Музыкальная группа Дежа вю обладает мощной качественной аппаратурой, которая позволяет наполнить приятным и плотным уху звуком как небольшое помещение (фуршет), так и огромное пространство (корпоратив до тысячи человек).

Игорь +7 916 623 4047, Андрей +7 910 483 8294 [/color]

24 Январь, 2010 13:13 
Anonymous Анонимный said...

[B]NZBsRus.com[/B]
Dont Bother With Sluggish Downloads Using NZB Files You Can Quickly Search Movies, Games, MP3 Singles, Software and Download Them at Dashing Speeds

[URL=http://www.nzbsrus.com][B]Usenet[/B][/URL]

04 Февраль, 2010 17:15 
Anonymous Анонимный said...

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

07 Февраль, 2010 12:42 
Anonymous Анонимный said...

Infatuation casinos? inquire this untested [url=http://www.realcazinoz.com]casino[/url] advisor and occasion up online casino games like slots, blackjack, roulette, baccarat and more at www.realcazinoz.com .
you can also discover peripheral exhausted our up to the cosset [url=http://freecasinogames2010.webs.com]casino[/url] orientate at http://freecasinogames2010.webs.com and prostrate brook show to compressed bread !
another unfrequented [url=http://www.ttittancasino.com]casino spiele[/url] appointment is www.ttittancasino.com , in inspection german gamblers, detract a pocket the extreme of the pay heed to goodness in humanitarian online casino bonus.

23 Февраль, 2010 08:25 
Anonymous Анонимный said...

Ну что вы тут раскричались? Пора успокоится и советую скачать Алису в стране чудес. Думаю просмотр успокоит немного нервишки;)

03 Март, 2010 23:26 
Anonymous Анонимный said...

Making money on the internet is easy in the underground world of [URL=http://www.www.blackhatmoneymaker.com]blackhat seo tools[/URL], It's not a big surprise if you haven’t heard of it before. Blackhat marketing uses not-so-popular or misunderstood methods to produce an income online.

20 Март, 2010 17:11 

Отправить комментарий

<< Home

Powered by Blogger