Кеширование динамического сайта
Автор (С): Дмитрий Турецкий

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

Для начала, давайте попробуем определиться с тем, что подразумевается под "динамическим сайтом". Динамика, как известно, означает изменение. То есть если мы, скажем, раз в месяц заменяем какую-то html-страничку на сайте, то он, в принципе, может считаться динамическим... Но понятно, что мы имеем в виду нечто совсем другое, а именно ситуацию, когда страница выводится каким-то скриптом. При этом, даже если содержимое этой выводимой странички не будет меняться, то сайт все-равно будет называться "динамическим". Занятно, не правда ли?

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

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

В заметке кешируем свой сайт говорилось, что определением параметров кеширования статичных файлов занимается веб-сервер. Поэтому, если мы не хотим мучаться с настройками скриптов, то самым простым способом обеспечить кеширование динамического сайта будет сохранение сгенерированных страниц в виде банальных html файлов. В некоторых случаях это может оказаться очень удобным решением - например, для сайта с большим количеством статей, на котором эти статьи еще и регулярно добавляются. Сами статьи не меняются (за очень редким исключением), а навигационные страницы можно перегенерировать каждый раз, при добавлении новой статьи. Одно из важных условий при использовании подобной схемы заключается в том, чтобы не генерировать весь сайт заново при каждом изменении, а заменять только те файлы, которые реально обновились - иначе сервер изменит Last-Modified заголовок для всех файлов и посетителям придется их качать заново.

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

Обратите внимание на следующие моменты:

Скрипты, использующие POST (в большинстве случаев) не кешируются. По возможности, используйте GET: кеширующие прокси ориентируются на URL документа. POST имеет смысл использовать только тогда, когда объем данных достаточно велик. Например, в поисковых запросах GET вполне применим, а если вы эти запросы проанализируете, то наверняка обнаружите, что множество из них повторяются весьма регулярно. К счастью, переделать скрипты с POST на GET совсем не сложно.

Еще более правильным решением будет использовать для передачи данных адрес, т.к. некоторые прокси отказываются кешировать страницы, в адресе которых используется знак вопроса. Эта рекомендация вряд ли применима к поиску, но вполне успешно может работать, скажем, при выводе статей - просто вместо адреса http://www.myhost.ru/article.cgi?id=14 появится что-то вроде http://www.myhost.ru/article/14/. Реализовать такой метод можно самыми разными способами - начиная с использования mod_rewrite и кончая написанием специального скрипта, который будет вызываться при возникновении 404-й ошибки.

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

При использовании Apache и mod_deflate вы столкнетесь с еще одной сложностью: во-первых для сжатых страниц не генерируется Content-Length, а во-вторых по умолчанию mod_deflate не сжимает страницы, запрашиваемые прокси (что снижает эффетивность кеширования, как для сервера - несжатые страницы забирают больше трафика, так и для пользователя - увеличивается время на получение страницы от прокси). Можно установить директиву DeflateProxied в on, но тогда есть риск, что какой-то пользователь, использующий старый браузер получит от прокси сжатую страницу и не сможет ее увидеть. Впрочем, процент таких пользователей весьма невелик и постепенно снижается, так что, пожалуй, им можно и пренебречь. Подробности о настройке mod_deflate можно почитать в документации.

Теперь пора прописывать заголовки. В зависимости от языка, на котором вы пишете свои скрипты для этого могут использоваться разные функции, например, в PHP есть специальная функция header(), а в Перле можно написать, скажем,
#!/usr/bin/perl
print "Content-type: text/html\n";
print "Expires: Thu, 08 May 2003 08:37:25 GMT\n";
print "\n";
Главное - не забыть, что заголовки должны быть выданы до того, как начнется вывод самого документа.

Для начала "обработаем" скрипты, которые кешироваться не должны. Т.к. "некешируемые" страницы чаще всего нужны для использования в защищенных областях - например, в панели администрирования - то проще всего использовать SSL для таких соединений. SSL страницы не кешируются и не расшифровываются прокси-серверами, поэтому ваши данные не станут достоянием гласности. Если же использовать SSL по каким-то причинам не хочется, то для начала сделайте ввод пароля и прочей информации через POST. Кроме того, установите в заголовках Last-Modified на текущее время (не забудьте, что должно указываться не местное время, а Гринвичское).
Last-Modified: Thu, 08 May 2003 08:37:25 GMT
Expires: 0
Cache-Control: no-cache, no-store, must-revalidate

Многие руководства рекомендуют устанавливать Expires на какую-то дату в прошлом (и в RFC написано, что так делать можно), но как показала практика, многие кеширующие прокси считают такое значение неверным и отбрасывают его (несмотря на то, что все в том же RFC2616 сказано, что неправильный формат заголовка Expires должен запрещать кеширование). А отбросив Expires и не видя других указаний, такие прокси используют свои настройки по умолчанию для кеширования документа. Именно поэтому для запрета кешиования лучше указывать Expires: 0 или указывать дату, скажем, на одну секунду вперед от времени запроса.

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

Ну и еще стоит воспользоваться META-тегами, о которых шел разговор в предыдущей заметке.

Теперь займемся остальными скриптами. Как правило, дата последнего изменения содержимого страницы скрипту известна - например, в случае списка статей можно запросить базу данных когда была добавлена последняя статья и указать эту дату в качестве Last-Modified. Среднее время обновления страниц можно либо вычислять тем же скриптом, либо просто определить "на глазок" и жестко прописать в скрипте. Скажем, уже опубликованная статья вряд-ли будет меняться чаще, чем раз в месяц, а спсок статей - раз в сутки. Исходя из этих данных генерируем заголовок Expires - просто прибавляем к текущему времени время жизни страницы и все.

По большому счету, на этом можно и закончить. Но можно и немножко продолжить, воздав должное появившимся в HTTP 1.1 функциям управления кешированием. Скажем, указать
Cache-Control: public, max-age=86400, must-revalidate
Вообще, заголовок Cache-Control дает довольно широкие возможности по управлению кешированием, например, там есть возможность запретить кеширование только каких-то определенных полей, но, к сожалению, стандарт этот поддерживается далеко не всеми прокси. Однако, если вы хотите, чтобы ваши скрипты были максимально эффективны, то стоит предусмотреть использование этого заголовка, т.к. постепенно число прокси-серверов, его понимающих, будет увеличиваться. Кстати, в своих скриптах стоит встроить разбор запроса - как минимум, "выкусывать" запрос "If-Modified-Since" и, если данные с того времени не изменились, выдавать в ответ код 304.

Дополнительно, можно еще прописать Expires в META-теге, правда, вряд-ли это даст какой-либо дополнительный выигрыш.

Hosted by uCoz