Пользователь
0,0
рейтинг
29 ноября 2010 в 22:42

Разработка → Заголовок Last-Modified, Symfony и ускорение поисковой индексации

Многие разработчики при создании сайтов забывают про очень полезный заголовок Last-Modified, благодаря которому можно оптимизировать загрузку web-страниц и облегчить работу поисковым роботам. Далее я постараюсь восполнить этот досадный пробел.


Для чего нужен заголовок Last-Modified?


Функцией заголовка как можно догадаться из названия служит информирование клиента о дате последней модификации web-документа. Исходя из спецификации rfc 2616, клиент может «спросить» у веб сервера не изменилась ли страница с определенного числа, послав серверу заголовок «If-Modified-Since». Если страница не изменилась сервер возвращает только заголовок «304 Not Modified», в противном случае – сервер возвращает заголовок «200 OK» и тело страницы. Как видно, выгода на лицо как для сервера, так и для клиента: браузер не будет грузить страницу снова и снова, а веб сервер будет отдавать меньше данных.



Какие сайты индексируются лучше? Динамические или статичные?


Пару лет назад среди SEO-шников водились споры по поводу того, какие сайты индексируются лучше? Динамические, написанные например на php, или статичные, без использования языков программирования. Зная о заголовке Last-Modified, можно ответить на этот вопрос. Все дело в том, что веб сервер сам обрабатывает заголовок «If-Modified-Since» если файл статичный. В случае динамической генерации страницы вся ответственность за ответ ложится на язык программирования и разработчика. А так как разработчик за частую не интересуется этим вопросом, то заголовки не отдаются вовсе.

Как заголовок Last-Modified ускоряет поисковую индексацию?


Все просто, как написано в помощи Яндекса, «робот не сможет получить информацию о том, обновилась ли страница сайта с момента последнего индексирования. А так как число страниц, получаемых роботом с сайта за один заход, ограничено, изменившиеся страницы будут переиндексироваться реже».

Представьте. Есть сайт с 10 тысячами страниц. Сайт написан на php. Не отдается корректно заголок Last-Modified. Поисковой робот не может получить информацию о том, обновилась ли страница сайта с момента последнего индексирования. Что он делает? Индексирует все страницы!!! А не только те, которые изменились.
Конечно! На многих сайтах используют Sitemap. Но Sitemap это рекомендация, помощь поисковому оптимизатору. Заменой заголовка Last-Modified он быть не может!

Настройка и обработка заголовока Last-Modified в php


Для того чтобы веб-сервер передавал php-backend'у заголовок If-Modified-Since необходимо ему от этом сообщить!

Для связки nginx + php так,
location ~ \.php$
{
  …
  if_modified_since off;
      
  fastcgi_pass  fcgi;
  fastcgi_index index.php;
  fastcgi_param SCRIPT_FILENAME /<путь > /web$fastcgi_script_name;
  …
  fastcgi_pass_header Last-Modified;
  include fastcgi_params;
}


* This source code was highlighted with Source Code Highlighter.


Для связки apache + php, так
# If-Modified-Since (if php is not installed as cgi then comment lines below)
RewriteRule .* - [E=HTTP_IF_MODIFIED_SINCE:%{HTTP:If-Modified-Since}]
RewriteRule .* - [E=HTTP_IF_NONE_MATCH:%{HTTP:If-None-Match}]


* This source code was highlighted with Source Code Highlighter.

Если php работает как модуль, то ничего настраивать не надо!

Простой php-код обработки запроса If-Modified-Since,
$qtime = isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])? $_SERVER['HTTP_IF_MODIFIED_SINCE']:'' ;

$modified = substr(gmdate('r', $timestamp), 0, -5).'GMT';

if ($hdr == $modified)
{
header ("HTTP/1.1 304 Not Modified ");
header ("Last-Modified: $modified");
exit();
}
header ("Last-Modified: $modified");
//render


* This source code was highlighted with Source Code Highlighter.


Как обрабатывать запрос If-Modified-Since в symfony?


В symfony уже предусмотрен механизм обработки заголовка. Все что нужно разработчику, так это передать в объект sfWebResponse заголовок. В случае его указания фрейморк все сделает сам.
$datestamp = time();
$response->setHttpHeader('Last-Modified', $response->getDate($datestamp));


* This source code was highlighted with Source Code Highlighter.

Так как на странице как правило располагается разный контент, то я написал метод, который выставляет самый поздний из переданных заголовков!
static public function setLastModified($datestamp)
{
    $response = sfContext::getInstance()->getResponse();
    $request = sfContext::getInstance()->getRequest();

    if(is_array($datestamp))
    {
      rsort($datestamp, SORT_NUMERIC);
      $datestamp = $datestamp[0];
    }

    if(!$response->hasHttpHeader('Last-Modified'))
    {
      $response->setHttpHeader('Last-Modified', $response->getDate($datestamp));
    }
    else
    {
      $origLastModified = strtotime($response->getHttpHeader('Last-Modified'));
      if($origLastModified < $datestamp)
        $response->setHttpHeader('Last-Modified', $response->getDate($datestamp));
    }
}


* This source code was highlighted with Source Code Highlighter.

Его очень удобно использовать в случае, если на странице, например, располагается 3 последних видеролика, 3 последних статьи и там еще что-нибудь. Загружая каждую модель из базы данных, мы можем вызывать метод, и в итоге в ответе получив самую позднюю дату модификации.
Для интересующихся код обработки заголовка находится в классе sfCacheFilter.class.php.

В заключение хочу сказать, что использование заголовка Last-Modified не всегда оправдано. Например, если на сайте 5 тысяч страниц и на каждой находится один и тот же блок с часто меняющимся контентом, использовать заголовок будет бесполезно! В этом случае можно разве что отдавать разные заголовки для клиентов и поисковых роботов. Но как по мне обман роботов ни к чему хорошему не приводит. Ну или убрать этот блок ;).

Еще,

Проверить сайт на корректную обработку заголовка можно тут или так,

<?php
$ch = curl_init();
  
  $url = 'http://site.ru/1.php ';
  
  curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, true);
    curl_setopt($ch, CURLOPT_NOBODY, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
      'If-Modified-Since: Sun, 28 Nov 2010 15:45:53 GMT'
    ));
   
  ob_start();
  curl_exec ($ch);
  curl_close ($ch);
  $data = ob_get_contents();
  ob_end_clean();
  
  
  echo nl2br($data);
?>

* This source code was highlighted with Source Code Highlighter.
Ярослав @frantic
карма
1,0
рейтинг 0,0

Похожие публикации

Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (25)

  • +1
    В рамках симфони и 10 000 страниц стоит объединить это дело с кэшированием.
    Строчек — не многим больше добавляется.
  • 0
    надо бы еще придумать, как прикрутить это к встроенному кешированию.
    • +1
      Вы не поверите. С отключенным кэшированием оно и работать не будет =)

      Поэтому я и написал, что довольно странно рассматривать эти вещи отдельно.
      • 0
        имелось в виду, к кешированию блоков и страниц симфони.

        • 0
          И я про то.
          Иначе откуда симфони возьмет последние время \ дату модификации блока )
    • 0
      Посмотрите в сторону Симфони 2.0. Знаю что там точно система кэширования выполняется перед инициализацией фреймворка, из-за чего прирост в производительности. Но по-моему там было что-то и с htaccess
      • 0
        2.0 это круто, но переводить несколько существующих, довольно сложных сайтов на настолько отличающийся фреймфорк, который к тому-же и довольно сырой, по меньшей мере рановато.
        • 0
          Полностью согласен. У самого было желание, но после ознакомления с сурсами и документацией оно само отпало :)
  • +3
    Часто меняющийся блок прогружать на страницу аяксом.

    Это единственное решение, которое я придумал, чтобы и роботов не обманывать и блок не убирать.
  • +1
    Не знаю как другие, но робот Яндекса совершенно равнодушен к Last-Modified.
    Плюс в другом, если уж задумались об этих заголовках — можно сразу прикрутить Expires для редко меняющихся или статических страниц. Тем самым снижаем нагрузку на сервер.
    • 0
      Вот он гад!!!

      95.108.158.242 [27/Nov/2010:05:30:14 +0600] «GET /uploads/autos/table/d/d3/d3a5b548c24b210c97e0b2a6cb32cb9b.jpg HTTP/1.1» 304 0 "-" «Mozilla/5.0 (compatible; YandexImages/3.0; +http://yandex.com/bots)»

      95.108.158.242 [27/Nov/2010:05:38:44 +0600] «GET /uploads/autos/table/0/09/0990b76daad021f3c454a1dad5743c4e.jpg HTTP/1.1» 304 0 "-" «Mozilla/5.0 (compatible; YandexImages/3.0; +http://yandex.com/bots)»
      • 0
        Как Вы наверное уже заметили, это робот YandexImages. Также поступает и YandexBlogs. Робот большого поиска тупо индексирует всё подряд.
        Googlebot любит Last-Modified, но на ранжирование вся эта информация никак не влияет.
        • 0
          Разговор шел за скорость индексации, а не ранжирование.

          Я питаю слабость к СЕОшникам, которые не умеют пользоваться поисковиками.

          Я намекну.

          www.google.com/search?q=%22htm+-+304%22+YandexBot
          • 0
            Тогда уж скорость не индексации, а кравлинга.

            • 0
              Тогда и это неизвестно. Ведь до проведения эксперимента не известно запрашивает робот страницы чаще, если получает 304 ответы.
              • 0
                Я вас уверяю — скорость кравлинга от этого никак не зависит. Инфа 100% :-)
                • 0
                  Ведь Яндекс к ним равнодушен =)
    • 0
      т.е. он не посылает заголовок if-modified-since при повторном проходе?
      • 0
        Да даже если и посылает. Кравлинг далеко не самая долгая составляющая в процессе индексирования.
    • +2
      Очень интересная ситуация!

      Из access.log
      87.250.254.242 - - [30/Nov/2010:23:59:56 +0300] GET /slovo/poizi/671.htm HTTP/1.1 "200" 4240 "-" "Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/ "-"

      * This source code was highlighted with Source Code Highlighter.


      Из моего кэтчера пауков:
      Nov 30 23:59:56 symfony [err] [Yandex] Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots) [If-Modified-Since: Tue, 23 Nov 2010 02:59:34 GMT][<хост>/slovo/poizi/671.htm]

      * This source code was highlighted with Source Code Highlighter.


      Код кэтчера такой:
      $pattern = '/(google)|(yandex)|(scooter)|(stack)|(aport)|(lycos)|(fast)|(rambler)/msi';

          if(isset($_SERVER['HTTP_USER_AGENT']) && preg_match($pattern, $_SERVER['HTTP_USER_AGENT'], $out))
          {
            $bot = self::$se[strtolower($out[0])];
            $agent = $_SERVER['HTTP_USER_AGENT'];

            $message = '['.$bot.'] '.$agent.' [If-Modified-Since: '.(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : 'no').']['.$request->getUri().']';
            sfContext::getInstance()->getLogger()->err($message); 
          }


      * This source code was highlighted with Source Code Highlighter.


      Получается робот все спрашивает как надо!

  • 0
    If-Modified-Since запрашивается браузером по собственной инициативе или только в случае, если на свой первый запрос (когда страницы в его кэше ещё не было) он получил какие-то заголовки от сервера? Две ситуации интересует: сервер хочет чтобы его никогда спрашивали (даже если клиент умеет) и сервер хочет, чтобы его спрашивали всегда (если клиент умеет, конечно). По здравому размышлению браузер должен спрашивать, если Last-Modified был в ответе на первый вопрос, и не должен, если не было. А как в реальности? Да, и кажется, по стандарту браузер может спросить HEAD, а не GET запросом. Пользуются они этим.

    P.S. Часто изменяющимся, но особо не значимым блоком можно, имхо, пренебречь при высоких нагрузках

    • 0
      Я с трудом представляю, что вам мешает проверить это.

      HEAD от браузеров я не видел.

      Как только проверите, узнаете, что еще есть Etag (привет nginx) и Cache-Control, а там и до чтения RFC не далеко.
      • 0
        Прежде всего интересует поведение «самого популярного браузера», запустить который на своей машине я по некоторым причинам не могу. Ну а после того, как ни один из проверенных мною браузеров корректно не обработал 301 и 302 коды возврата для POST запросов согласно RFC 2616 я предпочитаю уточнить у знающих людей насколько полно интересующие меня пункты реализованы.
    • +1
      Точно знаю что If-Modified-Since в FireFox 3.6 зависит от F5/Ctrl+F5.

      Если открыть html страницу и нажать F5, запросы css/js файлов будут всегда (даже если время жизни кэша не истекло) делаться с If-Modified-Since (ответ сервера 304).
      Ну и при нажатии Ctrl+F5 будут запросы без If-Modified-Since (ответ сервера 200).

      Возможно есть ещё какие-то тонкости с BFCache и валидностью кэша (expires, etag).

      Т.е. скорее всего ответ на ваш вопрос: да, браузер сам решает какие заголовки включить в запрос.
      Про явное указание поддержки If-Modified-Since сервером кленту я ничего не знаю.

      И ещё в обработке If-Modified-Since запросов есть ньюанс, некоторые версии IE отсылают заголовок в таком виде:
      If-Modified-Since: Fri, 02 Nov 2007 09:50:36 GMT; length=13801
      При ручном парсинге даты надо не забывать отбрасывать длину файла.

      Как-то так :)
      • 0
        If-Modified-Since не будет взят из головы браузером и добавлен к запросу, если он не получал до этого дату последнего изменения документа.

        В этом и был запрос.

        Вы рассказываете об очевидной ситуации «пользователь заставил браузер забыть про имеющийся кэш».

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