Кеширование динамического сайта
Автор (С): Дмитрий Турецкий
В заметке кешируем свой сайт мы начали разговор о кешировании сайта. Но там, в основном, речь шла о статическом содержимом, а ведь не секрет, что все большее число сайтов переходят на "динамику". Поэтому сегодня мы поговорим о том, как использовать преимущества кеширования при разработке динамического сайта.
Для начала, давайте попробуем определиться с тем, что подразумевается под "динамическим сайтом". Динамика, как известно, означает изменение. То есть если мы, скажем, раз в месяц заменяем какую-то 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-теге, правда, вряд-ли это даст какой-либо дополнительный выигрыш.