Предыстория

Эта статья была задумана и написана благодаря форуму XPoint.

Методы, описанные ниже, не являются личным изобретением автора. Просто он, будучи совсем еще чайником, лично сталкивался с описываемой проблемой и не смог найти толкового материала на предмет ее решения. Решение было найдено путем прочтения десятка статей сколь-нибудь близкой тематики, мученья людей с форума XPoint и недельной ночной баталии с PHP.

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

Внимание: не факт, что Вам все удастся сделать с первого раза за полчаса. Факт, что прежде чем что-то делать, никогда не лишне почитать о том, как это делали другие.

О чем эта статья

Если Вы когда-нибудь занимались созданием динамического сайта, то есть сайта, состоящего не из статичных HTML страничек, а скриптов, взаимодействующих с разными файлами и базами данных, Вы наверняка сталкивались (а если нет, то еще столкнетесь) с такими проблемами:

* «правильное» кэширование;
* «правильные» HTTP заголовки


Поясняю первый пункт. Кэширование — механизм, позволяющий клиенту (то есть пользователю на том конце соединения, точнее — его браузеру, для пользователя этот процесс незаметен) при просмотре одних и тех же файлов (например, картинок, составляющих элементы дизайна сайта, или файлов стилей CSS) не скачивать их каждый раз заново, а скачивать только однажды, а затем, по мере необходимости, использовать сохраненную на компьютере клиента копию. Принцип его работы примерно таков: при возникновении очередной необходимости скачать файл клиент обращается к серверу с запросом, не был ли файл изменен (не устарела ли копия на машине клиента), и если нет, не скачивает его заново, а использует сохраненный вариант. В браузерах, точно придерживающихся спецификации протокола HTTP, можно также добиться того, что запросы вообще не будут посылаться каждый раз, если есть сохраненная копия файла и точно известно, что она не успела устареть. Прелесть в том, что трафик (объем скачанных из Интернет данных) клиента уменьшается, и пользователь видит, что «сайт работает быстро». Естественно, уменьшается и трафик сервера, что позволяет снизить нагрузку на сервер и даже сэкономить, если Вы пользуетесь платным хостингом.

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

Поясню теперь пункт второй. Когда клиент запрашивает у сервера файл по протоколу HTTP, он, кроме содержимого самого файла (код в случае HTML файла, текст в случае текстового файла и т.п.), получает также HTTP headers — заголовки HTTP. Это служебные текстовые поля с информацией, которая не отображается в браузере, но интерпретируется им и, в основном, служит для сообщения клиенту данных о запрашиваемой странице.

Заголовок ETag («объектная метка»), например, служит для присвоения каждой странице уникального идентификатора, который остается неизменным, пока страница не модифицирована, и изменяется, если изменились данные на странице. Этот заголовок сохраняется на клиенте, и в случае необходимости повторного скачивания «меченой» страницы позволяет браузеру обращаться к серверу с запросом 'If-None-Match' — в таком случае сервер должен по значению ETag-метки сохраненной на клиенте копии определить, не устарела ли она, и если нет, ответить кодом '304 Not Modified' («не модифицировано»), и страница не будет скачена еще раз.

Заголовок Last-Modified («последнее изменение») предназначен для того, чтобы сообщить клиенту дату и время, когда последний раз изменилась запрашиваемая страница. Используя его, клиент, подобно случаю с ETag, может обращаться к серверу с запросом 'If-Modified-Since' — в этом случае сервер должен сравнить дату последней модификации копии, сохраненной на клиенте, с актуальной датой последней модификации. Если они совпадут, это значит, что копия в кэше клиента не устарела, и повторное скачивание не нужно (код ответа '304 Not Modified'). Last-Modified также необходим для корректной обработки Вашего сайта роботами - спайдерами (спайдер, англ. «паук» — это робот, который ходит по паутине Интернета и индексирует сайты, чтобы их можно было найти через поисковые системы, например, Google), которые используют информацию о дате модификации страниц в целях сортировки результатов поиска по дате, а также для определения частоты обновляемости Вашего сайта (см. например, что об этом пишет Яndex).

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

Есть еще заголовок Expires («истечение») — он сообщает браузеру, какой временной промежуток можно считать, что копия страницы в кэше свежа, и вообще не обращаться к серверу с запросами. Это удобно для таких файлов, о которых вы точно знаете, что они не изменятся ближайший час/день/месяц: фоновая картинка страницы, например. К сожалению, поддерживается не всеми браузерами. В рассматриваемом примере Expires будет равен десяти минутам, что подходит для большинства сайтов, на которых информация обновяется не слишком часто (~ раз в час).

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

На первый взгляд все очень сложно и непонятно, но пугаться не стоит, потому как система, которая будет рассмотрена далее, достаточно проста. Сложнее понять то, что нам нужно сделать, чем то, как.

Теория

Для решения вышеописанных проблем необходимо:

1. "Отловить" со страницы ту часть контента, за которой мы "следим";
2. Сравнить то, что получилось, с тем, что получилось в прошлый раз — для этого использовать БД, где и хранить информацию о страницах;
3. В случае модификации данных — обновить информацию о странице в БД;
4. Отослать клиенту HTTP заголовки в зависимости от его запроса.


Сделать это можно разными способами. Мы будем рассматривать систему, написанную на языке PHP, так как этот язык сейчас популярен, довольно прост для понимания и поддерживается большинством хостингов, в том числе и бесплатными. Данные мы будем хранить в БД MySQL по тем же причинам.

Итак, что там у нас там, на сайте? Динамические страницы, то есть PHP скрипты. Как функционирует эта «динамика»? Если сайт у Вас небольшой, то, скорее всего, у Вас под каждую страницу существует свой скрипт: index.php – для главной страницы, news.php для страницы новостей и т.п. Если же сайт у Вас выходит за рамки «домашней странички» и имеет сложную структуру, свой форум или пользовательскую зону, построен с использованием баз данных, то, вероятно, одним скриптом типа showpage.php генерируются сотни концептуально различных страниц (например, страницы форума генерирует один скрипт, но страницы-то разные, и надо следить за каждой отдельно). Первый случай проще для рассмотрения, хотя, если Вы поймете суть предлагаемой системы, Вы сможете без особых проблем интегрировать ее и на сайте, описанном во втором случае. А мы рассмотрим случай первый.

Чего надо «избежать» при «отлове» контента? Баннеров, счетчиков и всего, что написано в меню. Это не принципиально — Вы сами решаете, какой объем информации, отдаваемой Вашими скриптами, будет использоваться для определения их модификации.

Если «нужная» информация и «ненужные» примочки выводятся разными функциями, то все «нужное» можно просто забить в одну переменную. Что-нибудь вроде этого:

<?php

// Это – только кусок гипотетического PHP файла.
// Он не имеет никакого смысла, и используется только как пример.

include 'settings.inc.php'; // Подключаем какие-нибудь настройки, модули, классы и т.п.
print page_header(); // Выводим шапку – нам она не нужна;
print banners(); // Показываем баннеры – аналогично;

print $contentmonitoring_var = main_info(); // Показываем "основную" информацию;
// это как раз то, что нам нужно - кладем данные в переменную $contentmonitoring_var

print $temp = info2(); // Еще какая-нибудь нужная информация;
$contentmonitoring_var .= $temp; // добавляем данные в ту же переменную

print something_other(); // Не то;
print page_footer(); // Опять не то...

?>

Если Ваш сайт слишком громоздкий и сложный для такой модификации, есть более простой выход: буферизация вывода, стандартная функция PHP (Когда буферизация вывода активна, все, что генерирует скрипт, не высылается клиенту, а сохраняется во внутреннем буфере.) То, что попало в буфер, можно положить в переменную и работать с этими данными, как с обычной строковой переменной, а потом отослать клиенту. Тогда Вам необходимо просто найти в коде начало и конец того куска выводимой информации, которую надо проверять, и маркировать этот кусок HTML-комментариями. Для этого перед началом вывода интересующей нас информации надо вставить строчку

<!--content-->

а после — строчку

<!--/content-->

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

Теперь мы отделили «мух от котлет» и информация, которую будем «контент-мониторить», заключена у нас между HTML-комментариями. Остается включить буферизацию, в конце скрипта взять данные из буфера, выловить оттуда часть кода между комментариями, и обработать его на предмет модификации. По результатам этой обработки, а также в зависимости от запроса клиента, отослать HTTP заголовки. Все это будет делать скрипт, который надо подключить ко всем Вашим скриптам. Делается это так: в начале каждого файла, сразу после строчки <?php вставляем код

ob_start(); // Слежение за контентом – запускаем буферизацию.

а в конце, перед строчкой ?> код

include_once('content_monitoring.inc.php'); // Слежение за контентом – подключаем исполняемый скрипт.

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

Вот и вся теория, в общем-то. Добавлю еще, что данную систему можно расширить — например, сюда же можно присовокупить счетчик посещений. Но это уже выходит за рамки тематики данной статьи.
Information
  • Posted on 27.04.2013 16:03
  • Просмотры: 193