Заголовок 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.
    Метки:
    Поделиться публикацией
    Похожие публикации
    Комментарии 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 не будет взят из головы браузером и добавлен к запросу, если он не получал до этого дату последнего изменения документа.

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

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

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