Правильный ответ сервера: заголовки Last-Modified и if-modified-since

"Все аспекты самостоятельного создания и продвижения сайтов
от практика с многолетним опытом." — блог Рудь Сергея
info@site-on.net
Заметка: активирована адаптивная версия сайта, которая автоматически подстраивается под небольшой размер Вашего браузера и скрывает некоторые детали сайта для удобства чтения. Приятного просмотра!
08.06.2013

Здравствуйте уважаемые читатели блога Site on! Мы продолжаем тему внутренней оптимизации сайта, одного из важнейших факторов SEO. Эта статья затронет то, что можно назвать тонкостями внутренней оптимизации, так как речь пойдёт о коде ответа, который получат поисковые системы и посетители в ответ на их обращение к странице.

Правильный ответ сервера

Несмотря на то, что это довольно мелкая деталь при построении и оптимизации сайта в целом, однако она очень важна! А именно важно, чтобы страница, на которой не было изменений с последнего визита робота или человека отдавала 304 код, который означает, что страница осталась без изменений. Когда сервер отдаёт клиенту этот код, то выполнение всех PHP сценариев на странице даже не начинается, вместо этого страница загружается из кэша, что значительно снижает нагрузку на сервер и ускоряет загрузку страницы у пользователя.

Таким образом, настроив правильные ответы нашего сервера, мы убиваем сразу как минимум пять зайцев:

Почему-то для меня последний пункт кажется самым сладким (так как влияет на SEO и повышает доверие к вашему сайту у поисковых систем), хотя без сомнения остальные пункты тоже чрезвычайно важны.

Как настроить 304 и 200 ответы сервера?

Мы уже сказали о том, что в ответ на запрос к неизменившимся страницам сервер должен отдавать 304 Not Modified, а какой код сервер должен отдавать, если клиент обращается к странице первый раз или обращается к изменившейся странице? В таких случаях сервер должен отдавать статус 200 OK. Специально данный код посылать не нужно, если со страницей всё в порядке, то она всегда выдаёт 200.

Поэтому нам нужно позаботиться только о 304 коде, так как его, сервер без нашего вмешательства не пошлёт. Для этого нам поможет веб-ориентированный язык PHP, а также заголовок Last-Modified и запрос if-modified-since.

Заголовки Last-Modified и if-modified-since

Last-Modified – это заголовок, который мы посылаем с помощью PHP, данный заголовок содержит точное время последнего изменения страницы (в секундах). Для этого используется общепринятая мера измерения времени: Unix Time Stamp.

Unix time stamp – это число секунд, прошедших с начала эпохи Юникс: 1 января 1970 года. На момент написания этого предложения Unix time stamp равняется 1370597447 секунд – это 07.06.2013 09:30:47 GMT (+00:00).

То есть все, что нам нужно делать, это всего лишь посылать PHP заголовок с инструкцией Last-Modified и нужной датой:

header('Last-Modified: '.gmdate('D, d M Y H:i:s', $last_modified_time).' GMT');

Где header – это конструкция для отправки HTTP заголовка, Last-Modified – то, что мы отправляем и сразу после двоеточия идёт его значение:

gmdate('D, d M Y H:i:s', $last_modified_time).' GMT'.

В роли значения Ласт-модифайд выступает функция gmdate(), которая содержит придуманную мной переменную $last_modified_time (вы можете назвать как угодно). В переменной $last_modified_time и содержится время последнего изменения в формате Unix Time Stamp, а функция gmdate() служит нам для того, чтобы привести дату в надлежащий вид (время по Гринвичу).

Для наглядности вот вам пример: если мы в функцию gmdate() положим значение 1365003142, то на выходе получим: Wed, 03 Apr 2013 15:32:22.

Теперь, когда мы узнали, как происходит весь процесс, может возникнуть вопрос: «Это что, для каждой странице нам вручную нужно указывать время последнего изменения?». Ответ: «Да!». Лично я делаю именно так – вручную, самый надёжный вариант. Однако конкретно для данного блога я всё предусмотрел, к примеру, если появляется новый комментарий на странице, то в переменную $last_modified_time заносится время добавления этого комментария, это сделано для того, чтобы поисковые системы смогли проиндексировать новые комментарии и знали, что сайт «живой». Каждый сайт индивидуален и вам придётся придумать свой собственный алгоритм по указанию даты последнего изменения страницы, или всегда указывать её вручную.

Ещё раз подчеркну, у меня алгоритм таков:

1) я указываю дату создания материала вручную, если я меняю что-то в статье (опечатки или дописываю), то затем я опять-таки вручную вписываю новое время последнего обновления.

2) Если посетитель добавляет комментарий, то в переменную $last_modified_time автоматически, без моего ведома заносится время добавления комментария, так как фактически это и будет датой последнего изменения страницы.

Чего я не учёл: в правой колонке сайта у меня находятся свежие статьи, рекомендуемые и топ-10. Они меняются постоянно и при этом одновременно для всех страниц. Если бы я при каждом изменении правой колонки сайта менял (автоматически или вручную – не важно) дату последнего изменения страницы, то потерялся бы весь смысл этого действия. Я решил, что эти изменения отслеживать и учитывать при указании $last_modified_time не стоит, так как они не несут в себе пользы для SEO.

Как я уже писал, я не могу указать вам, как именно автоматизировать дату последнего изменения страницы, но я скажу вам, как этого делать НЕ нужно!

Ошибки при указании даты последнего изменения

Первое что может прийти в голову большинству людей, это в заголовке посылать дату последнего изменения файла с содержимым страницы. Лично у меня тексты статей лежат в файлах, а не в базе данных, так что для меня такой способ мог бы показаться отличным выходом, чтобы не вводить каждый раз Unix Time Stamp вручную. Но нет! Большинство хостингов, а может даже все, за дату последнего изменения файла берут дату его создания, они не учитывают последующие его изменения.

Я думаю, последствия в таком случаи вам понятны. Один популярный украинский хостинг провайдер (и думаю не он один) в своём FAQ пишет что-то вроде: «Вместо даты последнего изменения файла используйте функцию time(), которая возвращает текущее время в формате Unix time stamp». Вот так абсурд! Это же просто на месте застрелится! И этот хостинг-провайдер считается «одним из лучших», после того как я это прочитал, я сразу же перехотел становиться их клиентом.

Это просто анти-SEO, сами подумайте, заходит к вам на страницу поисковичёк и смотрит: «Ух ты ж-ка! Последнее время изменения страницы было только что, вот это я угадал когда прийти, класс!». Заходит он через пару дней на эту же самую страницу: «Гляди-ка, опять только что изменилась, вот это совпадение… Погодите, а почему я не вижу никаких изменений? Ладно, приду в другой раз». Приходит снова: «Ну нет мужики, это уже не смешно, доверять вам точно нельзя». Вот такая вот сказочка :)

А потом люди удивляются, почему результаты в поисковой выдачи не такие как хотелось бы, да потому что к вашему сайту теряется банальное доверие (trust). Прям как в притче "Про пастуха и волков".

Итак, с основными ошибками разобрались: нельзя указывать текущее время и не советую указывать время изменения файла. Теперь продолжим разбирать как это всё работает.

Настроить отсылку заголовков Last-Modified это ровно 1/3 дела, нам ещё предстоит: сделать ответ на запрос if-modified-since и включить кэширование страницы. Оба эти действия не займут много времени и строк кода.

if-modified-since

if-modified-since

if-modified-since – это запрос клиента к вашему серверу, в нём клиент спрашивает: «не изменилась ли страница с моего последнего визита?». Если страница не изменилась, то мы должны остановить выполнение дальнейшей загрузки страницы командой:

die

При этом тело страницы не должно начать отрисовываться, это всё происходит ДО первого вывода чего-либо на страницу! Вместе с этим необходимо вернуть клиенту ответ сервера 304 Not Modified, тем самым сказав, что страницу нужно взять из кэша. Давайте сразу к делу:

if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $last_modified_time){
    header('HTTP/1.1 304 Not Modified');
    die;
}
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $last_modified_time).' GMT');

Итак, в первой строке мы с помощью условного оператора PHP (if) проверяем, пришёл ли к нашему серверу запрос HTTP_IF_MODIFIED_SINCE, а также сразу проверяем число секунд в пришедшем HTTP_IF_MODIFIED_SINCE больше, чем в $last_modified_time или нет? Если больше, значит дата последнего визита клиента позже, чем дата последнего изменения страницы, отсюда делаем чисто логический вывод, что страница не изменилась, а значит второй строчкой отправляем ответ сервера 304 Not Modified и 3 строчкой убиваем (прекращаем) выполнение всех сценариев на странице. Другими словами прекращаем её загрузку.

Если же клиент не послал нам запрос HTTP_IF_MODIFIED_SINCE или его последний визит оказался раньше, чем дата последнего изменения страницы, то мы (по умолчанию) отдаём код 200 ОК и пятой строкой посылаем ему АКТУАЛЬНУЮ дату изменения страницы, вместо той, что была у него.

Про IF_MODIFIED_SINCE и как устроен код рассказал вам всё что нужно, кроме того, что делает функция strtotime():

strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])

Внимательный и смекалистый читатель уже мог догадаться, что эта функция конвертирует обычную дату в Unix time stamp, так как переменную $last_modified_time мы задаём именно в нём, а потому для сравнения нам необходимо привести всё к общему знаменателю общей системе измерения.

И последнее, нам остаётся только включить кэширование, это делается с помощью следующих строк:

header("Cache-Control: public");
header("Expires: " . date("r", time()+10800));

Где число 10800 это время (в секундах) на которое мы хотим закэшировать страницу, то есть в данном примере на 3 часа.

И как всегда для тех, кто ничего не понял выкладываю всё полностью, как это устроенно у меня на блоге:

<?php
header("Cache-Control: public");
header("Expires: " . date("r", time()+10800));

if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $last_modified_time){
    header('HTTP/1.1 304 Not Modified');
    die; /* убили всё, что ниже */
}
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $last_modified_time).' GMT');
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru-ru" lang="ru-ru" dir="ltr">
И пошла поехала вся остальная часть страницы

Думаю, вы могли заметить, что вся эта история с Ласт-модифайд является аналогом тега в sitemap.xmllastmod. Так вот lastmod носит ознакомительно-рекомендательный характер, а с ответами вашего сервера никто не поспорит. Естественно, не редкость, когда lastmod в карте сайта отличается от заголовка Ласт-Модифайд, однако с этого момента они должны быть у вас одинаковы! Мы ведь теперь с вами какую науку изучили, не для того чтобы уподобляться горе-вебмастерам, которые дальше sitemap.xml не продвинулись.

Лично я в данный момент вообще не пользуюсь тегом lastmod в своих картах сайтах, возможно, позже я пересмотрю свои действия, но пока что не вижу смысла быть настолько скурпулёзным, имея правильные заголовки Last-Modified :)

И напоследок, проверить корректность Last-Modified и if-modified-since вы можете с помощью этого сервиса: клик.

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

С уважением, .
Пожалуйста, оцените эту статью
Средняя оценка: 4.38 из 5 (проголосовало: 29)
Статья оказалась вам полезной? Подпишитесь, чтобы не пропустить новые!

Ваш email:
Вы можете помочь развитию проекта, сделав всего 1 клик:
Спасибо!
Пожалуйста, прокомментируйте, как Вам моя статья?
Имя:
Комментарий:

Если Вы хотите вставить код, пожалуйста, заключайте его в [code][/code]

Подписаться на новые комментарии:

E-mail:


Защита от спама: пожалуйста, напишите слово "сел" справа налево
Ответ:
Подписаться на новые комментарии без комментирования - Email:
Защита от спама: пожалуйста, напишите слово "сел" справа налево
Ответ:

07.11.2013 07:18:47 Volya:
Здравствуйте! Очень полезная статья, спасибо! Я понимаю, как это реализовать на статических страницах, но не понимаю, как на Джумле можно вставить код в каждую страницу? Поясните, пожалуйста! Можете прописать путь хотя бы только на главную (избранные материалы)? Заранее спасибо!

Ответить на комментарий


07.04.2014 08:26:36 Илья:
Добрый день, Сергей!

Сделал ответ сервера по аналогии с вашим примером. Проверка выдает следующее:

Документ получен!

Страница http://justcase.su вернула 304 код, но продолжила отдавать страницу.
Результат: бессмысленно

Сайт http://justcase.su корректно отдает код 304 Not Modified, но страница продолжает загружаться.

Еще и Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT

Не подскажете, где и что копать стоит?

Ответить на комментарий


19.07.2014 12:49:58 Алексей:
Очень нужно, но Не подходит для интернет магазинов! В частности для скрипта Joomshopping.
Корзина товаров не работает, или работает некорректно. При просмотре страниц товара, показывает неверные данные о кол-ве товаров в корзине, на странице корзины товаров показывает 0 товаров, хотя в корзине товары есть. Полный глюк.
19.07.2014 17:14:08 Сергей отвечает:
Никто и не обещал, что будет легко :) Всегда нужно допиливать под свои нужды.

Ответить на комментарий


01.11.2014 23:20:51 Максим:
Подскажите, откуда берутся эти даты? http://joxi.ru/jgmvQ05TxE6Pra

Установил вот этот код для возврата правильных заголовков с сервера HTTP_IF_MODIFIED_SINCE
http://joxi.ru/Y82QGkxC1zQlrd

вот тут проверял: http://last-modified.com/ru/

почему то старые даты, не пойму откуда они, с сервера берутся откуда то ил движок сайта такие генерирует?

И если ранее был криво настроен код отдачи этих заголовков, могло это отразиться на индексации страниц, и каких либо реакций от ПС Яндекс?

Ответить на комментарий


27.11.2014 18:38:34 fenix-77:
Зробив реалізацію last-modified на Joomla 2.5 але в K2 регулярно оновлюються коментарі і вони в індекс не попадуть поки не змінити статтю.
Вирішив це модифікацією K2 файлу Z:\home\localhost\www\shop\components\com_k2\models\item.php після if ($row->published) приблизно 1264 рядок і до $mainframe->close(); замінити все цим:
{
$caching = K2_JVERSION == '30' ? $config->get('caching') : $config->getValue('config.caching');
if ($caching && $user->guest)
{
$id=JRequest::getInt('itemID');
$datenow = JFactory::getDate();
$dn = $datenow->toMySQL();
$db = JFactory::getDBO();
$db->setQuery("UPDATE #__k2_items SET modified='{$dn}' WHERE id={$id}");
$db->query();
$response->message = JText::_('K2_THANK_YOU_YOUR_COMMENT_WILL_BE_PUBLISHED_SHORTLY');
echo $json->encode($response);
}
else
{
$id=JRequest::getInt('itemID');
$datenow = JFactory::getDate();
$dn = $datenow->toMySQL();
$db = JFactory::getDBO();
$db->setQuery("UPDATE #__k2_items SET modified='{$dn}' WHERE id={$id}");
$db->query();
$response->message = JText::_('K2_COMMENT_ADDED_REFRESHING_PAGE');
$response->refresh = 1;
echo $json->encode($response);
}

}
else
{
$id=JRequest::getInt('itemID');
$datenow = JFactory::getDate();
$dn = $datenow->toMySQL();
$db = JFactory::getDBO();
$db->setQuery("UPDATE #__k2_items SET modified='{$dn}' WHERE id={$id}");
$db->query();
$response->message = JText::_('K2_COMMENT_ADDED_AND_WAITING_FOR_APPROVAL');
echo $json->encode($response);
}

}

Ответить на комментарий


10.01.2015 23:21:09 Вячеслав:
Спасибо автору, всё очень доходчиво. А самое главное, что до меня дошло, что это всё очень важно и нужно. Но меня теперь терзает один очень щепетильный вопрос. А как же всё же быть с динамическим контентом???!! Например, у меня реализована возможность авторизации, с автоматической подстановкой имени авторизованного пользователя и (возможно) каких-либо еще параметров в формы, например комментариев и еще кое-какие фишки, например, каптча с использованием сессий, возможность переключения пользователем шаблона сайта с мобильной версии на полную и наоборот, не уходя с этой же страницы... в общем много чего реализовано именно динамического и в основном всё на php. Вот теперь я ломаю голову, как же мне реализовать все прелести изложенные в этой статье, да так что-бы не в ущерб всем своим фишкам. Неужели это не возможно? Ведь на строчке die закончится всё, что еще даже не успело начаться и в браузер загрузится кэшированная страница лишенная какой-либо динамики. Может быть есть какое-нибудь альтернативное решение? Интересно, как такие вопросы решают сайты социальных сетей, там же сплошная динамика.

Ответить на комментарий


10.01.2015 23:31:24 Вячеслав:
P.S. Проверил на http://last-modified.com/ru для интереса страницы сайтов Вконтакте и Habrahabr... так вот они заголовки Last-Modified не отдают. Как же такие монстры обходятся без этого важного наворота?

Ответить на комментарий


28.03.2015 17:44:57 Роман:
Автор почему-то не дописал самого главного в код, - значение перменной $last_modified_time. Поэтому у всех выдает "Thu, 01 Jan 1970 00:00:00 GMT".

В самом начале кода нужно дописать:

$last_modified_time = time();
28.03.2015 18:19:55 Роман отвечает:
Хотя нет, дописал, что сам задает. Но большинство же не знает как и просто копируют конечный код.
И я перепутал, вместо time нужно getlastmod. Будет подставлять дату последнего изменения страницы:
$last_modified_time = getlastmod();
Это для статических сайтов. В движках нужно соответствующие функции подставлять. В WordPress будет:
$last_modified_time = get_the_time('U');
14.04.2015 16:31:47 Александр отвечает:
А для magento какая функция выводит дату последнего изменения страницы, не в курсе кто?

(типа этой get_the_time('U');)

Ответить на комментарий

Использую для работы
Мои расширения
Свежие статьи
Рекомендую
Горячо обсуждаемые
Подписка
  • Следовать в twitter:
  • Подписаться по RSS:
  • Подписаться по E-mail:
  • Следить ВКонтакте:
  • Следить на Facebook:
Пользовательское соглашение об условиях использования сайта и Политика конфиденциальности
Перепечатывание или копирование материалов сайта (текста, изображений и другого содержимого) для их публичного или коммерческого использования в сети Интернет, либо в печатных изданиях строго запрещены. При нарушении данного правила, с нашей стороны будут предприняты соответствующие меры, вплоть до судебной жалобы.
© site-on.net
Шрифт: +стандартно-