Õàëòóðêè îò ïðîãðàììèðîâàíèÿ è WEB-ñòðîèòåëüñòâà

  Главная страница » Изба-читальня » Наклоноведение » Конференция » Ссылки » Френдюшник » Фотоальбом » mln

NB!: Эта заметка является продолжением статьи "RSS - в массы, а массы...", опубликованной на этом ресурсе. Поэтому, если Вы попали на эту страницу с помощью поисковой системы, то рекомендую начать именно с неё, т.к. в ней даются основные понятия, необходимые для понимания всего нижеописанного.


Егор Наклоняев

Обживаем RSS 2.0

Вводная часть

В процессе работы над трансляцией сайта в массы для удобства посетителей был написан RSS для форума на движке phpBB и на движке IkonBoard. Естественно, что в силу природной лени хотелось обойтись малой кровью и взять готовое решение, однако все существующие решения не устраивали по следующим причинам:

Данная статья посвящена описанию решения вышеперечисленных проблем с примерами на PHP. 

Проблема 1. Передача максимума информации агрегаторам.

Как показал опыт работы с многочисленными агрегаторами, не все из них понимают некоторые поля из спецификации RSS 2.0. В частности, Abilon и некоторые другие не понимают элемент category. Элемент author, согласно спецификации, должен обязательно содержать адрес электронной почты автора, что в наш век спама является далеко не лучшей идеей, и, к тому же, быть написанным латиницей. Таким образом, использование конструкции вида <author>Вася Пупкин</author> приводит к ошибкам при проверке валидатором. Следовательно, возникает мысль, что надо что-то делать. К счастью, решение существует. Достаточно расширить пространство имён (Namespace) нашего XML файла и добавить элементы, которые знакомы каждому уважающему себя агрегатору. Это пространство имен носит название Dublin Core Metadata и предназначено для комплексного описания электронных документов. 

Итак, что нужно сделать:

  1. Изменить заголовок, заменив <rss version="2.0"> на <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
  2. В элементе <item> вместо <author> использовать новый, подключенный таким образом,  элемент <dc:creator>
  3. Добавить элемент <dc:subject>, в котором описать раздел сайта в дополнение или вместо элемента category.

Результат приведен ниже, вставки выделены жирным, удаленное вычеркнуто. В качестве основы взят RSS, написанный в статье "RSS - в массы, а массы..."

Теперь, позволю себе поподробнее остановиться на новом пространстве имён, которое для удобства использования сведено в таблицу. Как нетрудно догадаться, все элементы должны начинаться с префикса "dc:", что определяется в процессе подключения пространства имён и может быть, в принципе, изменено. Однако существует устоявшаяся традиция, да и агрегаторы уже привыкли, так что выпендриваться в данном вопросе не будем. Следует еще учесть, что не все элементы понимаются большинством агрегаторов, т.е. не все элементы будут показаны пользователю, однако это не препятствует их использованию в случае необходимости. Кроме того, можно использовать и несколько однотипных элементов, например, два элемента dc:contributor в одном item.

Пространство имен http://purl.org/dc/elements/1.1/
Имя Соответствие
в RSS 2.0
Описание
contributor  Нет Соавтор, помошник. Элемент позволяет описать соавтора данного ресурса. 
Пример: <dc:contributor>Вася Пупкин</dc:contributor>
coverage Нет Месторасположение или временные рамки содержимого ресурса. Предназначение достаточно туманно описано, агрегаторами не понимается и способ практического использования непонятен 
creator author Автор ресурса. Рекомендую использовать вместо стандартного в RSS 2.0 <author>, т.к. не требует адрес электронной почты. Корректно понимается большинством агрегаторов
Пример: <dc:creator>Вася Пупкин</dc:creator>
date pubDate Дата, описывающая жизненный цикл ресурса. Записывается согласно стандарту в формате ISO 8601, например 2005-01-31T16:41:35Z или 2005-01-31T18:41:35+03:00. Особо не нужен, т.к. есть элемент <pubDate> в RSS 2.0
description description соответствует элементу <description> в RSS 2.0
format нет MIME формат. Особо не нужен.
identifier guid Уникальный идентификатор ресурса. Особо не нужен, т.к. вполне заменяется guid
language language Описание используемого языка, в отличии от элемента в RSS 2.0 можно использовать для каждой записи, если это нобходимо
Пример:
<dc:language>ru</dc:language>
publisher нет Издательство или имя, опубликовавшее ресурс. <dc:publisher>УЧПЕДГИЗ</dc:publisher> 
relation нет Ссылка на связанный ресурс.
rights copyright Авторские права на данный элемент или канал. 
source source Источник, откуда получен данный документ. В данном случае, лучше использовать стандартный элемент RSS 2.0
subject category Тема, раздел сайта. Понимается большинством агрегаторов и показывается в отдельной колонке. Рекомендуется к использованию вместо или совместно с элементом category
title title Заголовок ресурса. В данном случае, лучше использовать стандартный элемент RSS 2.0
type нет MIME тип ресурса. Особой необходимости в использовании нет.

Теперь немного о RSS для форумов. Дело в том, что хотелось бы получать иерархическую структуру, подобную News, вместо обычных последовательных записей. Насколько мне известно, единственным агрегатором который это делает является Newz Crawler. Для этого авторы программы используют пространство имен http://web.resource.org/rss/1.0/modules/annotation/, однако в варианте сделанном у них на форуме они подключают его неверно, что приводит к ошибкам при проверке валидатором. Итак, как это всё выглядит на практике.

Допустим, у нас имеется некая запись в форуме, например, http://mysite.ru/forum.cgi?r=1, на неё есть ответ http://mysite.ru/forum.cgi?r=2. Тогда связь описывается следующим образом.

<item>
<title>Родительская ссылка</title>
<link>http://mysite.ru/forum.cgi?r=1</link>
<guid isPermaLink="true>http://mysite.ru/forum.cgi?r=1</guid>
<annotate:reference rdf:resource="http://mysite.ru/forum.cgi?r=1" />
</item>
<item>
<title>Дочерняя ссылка</title>
<link>http://mysite.ru/forum.cgi?r=2</link>
<annotate:reference rdf:resource="http://mysite.ru/forum.cgi?r=1" />
<guid isPermaLink="true>http://mysite.ru/forum.cgi?r=2</guid>
</item>

Для включения этого пространства имен нужно не только использовать xmlns:annotate="http://purl.org/rss/1.0/modules/annotate/", но и xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" т.к. rdf:resource ранее нигде не был определен.

По большому счету, в данном случае авторы NewzCrawler используют далеко не самое красивое решение, и, вместо того, чтобы описать своё пространство имён используют бета-версию к RSS 1.0/RDF для формата RSS 2.0. Однако, если включить оба пространства имен, то и агрегатор будет работать и валидатор останется вполне удовлетворенным. В результате всех включений заголовок RSS начинает выглядеть так:

<rss version="2.0" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:annotate="http://purl.org/rss/1.0/modules/annotate/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">

Проблема 2. Обработка HTTP заголовков с целью уменьшения траффика

Очень многие программисты почему-то не используют эти мощные возможности HTTP протокола при программировании RSS каналов, и даже LiveJournal и тот каждый раз передаёт полностью всю ленту новостей. Эта тема уже поднималась в предыдущей статье, поэтому остановлюсь лишь на нескольких основных моментах.

1. Обязательно индексируете поле, содержащее дату и время создания записи. Это позволит выполнять очень быстро запрос SQL вида SELECT MAX(mydate) FROM mytable, что позволит включить в начало программного кода нижеследующую конструкцию, существенно уменьшающую нагрузку на сервер при обработке RSS запросов. В результате, в большинстве случаев вместо формирования ленты происходит возвращение ответа, что ничего не изменилось с последнего визита.

if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']))  
{
    $sql= "SELECT MAX(mydate)FROM mytable";
    $result = mysql_query($sql)or die("Query failed : " . mysql_error());
    $deadline=strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
    if($row=mysql_fetch_row ($result))
      {
            if($row[0]<=$deadline)
            {
                header("HTTP/1.1 304 Not Modified");
                exit;
              }
       }
}

2. Проверяйте заголовки $_SERVER['HTTP_IF_MODIFIED_SINCE'] и $_SERVER['HTTP_IF_NONE_MATCH'], в случае совпадения вычисленных заголовков с переданным возвращайте код 304 Not Modified

3. Передавайте вместе с RSS следующие заголовки

header ('Content-Type: text/xml; charset=windows-1251');
header("Last-Modified: ".$MyGMTtime);
header("Etag: ".$MyETag);

Вычисление переменных для  заголовков  можно сделать, например, соедующим образом: пусть имеется некая переменная $LastPostTime, которая содержит самую позднюю дату из ленты новостей, тогда:

$MyETag='"RSS'.gmdate("YmdHis", $LastPostTime).'"';
$MyGMTtime=gmdate("D, d M Y H:i:s", $LastPostTime)." GMT";

Другой способ вычисления значения Etag может быть построен на вычислении MD5-хэша всей ленты новостей. Это способ рекомендуется, когда содержимое достаточно динамично и при изменении содержимого не затрагивает временные метки. Т.е. если весь RSS собрался в переменной $out, то:
$MyETag='"'.md5($out).'"';

4. Используйте полученную через переменную $_SERVER['HTTP_IF_MODIFIED_SINCE'] дату для запроса к БД с целью уменьшения общего количества записей. Помните, основное назначение агрегатора накапливать данные, полученные с канала, дублировать записи особо не нужно.

5. Помните, что заголовки должны быть посланы ДО того как будут посланы любые данные, поэтому при необходимости, или собирайте всю ленту новостей в переменной, или используйте ob_start(); в начале программы.

Проблема 3. Сжатие GZIP

Сжатия содержимого ленты новостей это достаточно хорошее решение для уменьшения траффика. Не мудрствуя лукаво предлагаю просто использовать готовые конструкции из форума phpBB

//
// gzip_compression
//
$do_gzip_compress = FALSE;
$phpver = phpversion();
$useragent = (isset($_SERVER["HTTP_USER_AGENT"]) ) ? $_SERVER["HTTP_USER_AGENT"] : $HTTP_USER_AGENT;
if ( $phpver >= '4.0.4pl1' && ( strstr($useragent,'compatible') || strstr($useragent,'Gecko') ) )
{
	if ( extension_loaded('zlib') )
	{
		ob_start('ob_gzhandler');
	}
}
else if ( $phpver > '4.0' )
{
	if ( strstr($HTTP_SERVER_VARS['HTTP_ACCEPT_ENCODING'], 'gzip') )
	{
		if ( extension_loaded('zlib') )
		{
			$do_gzip_compress = TRUE;
			ob_start();
			ob_implicit_flush(0);
			header('Content-Encoding: gzip');
		}
	}
}
// end gzip block
// Здесь пойдет программа вывода ленты новостей
//
// Compress buffered output if required and send to browser
//
if ( $do_gzip_compress )
{
	//
	// Borrowed from php.net!
	//
	$gzip_contents = ob_get_contents();
	ob_end_clean();

	$gzip_size = strlen($gzip_contents);
	$gzip_crc = crc32($gzip_contents);

	$gzip_contents = gzcompress($gzip_contents, 9);
	$gzip_contents = substr($gzip_contents, 0, strlen($gzip_contents) - 4);

	echo "\x1f\x8b\x08\x00\x00\x00\x00\x00";
	echo $gzip_contents;
	echo pack('V', $gzip_crc);
	echo pack('V', $gzip_size);
}
exit;

Sapienty Sat

   Обсудить
в форуме
   Написать
авторам
   Добавить
ссылку
   Посмотреть
ссылающиеся
страницы
   О проекте,
политика сайта
  Дизаин сайта © 2001-2004 ЕМН
Автор текста:Егор Наклоняев
Labelled with ICRA