Skip to content

Instantly share code, notes, and snippets.

@aktaumag
Created March 13, 2021 21:00
Show Gist options
  • Save aktaumag/4e036bff9cd32a74dc267800091e2c65 to your computer and use it in GitHub Desktop.
Save aktaumag/4e036bff9cd32a74dc267800091e2c65 to your computer and use it in GitHub Desktop.
Обеспечение корректной обработки If-Modified-Since имеет чрезвычайно важную роль, если мы боремся за органический трафик.
@aktaumag
Copy link
Author

aktaumag commented Mar 14, 2021

Устанавливаем заголовки при помощи PHP

Важно: Не применять это к корзине, избранным и прочим динамическим страницам, которые меняются в зависимости от параметров и т.д. Все такие страницы всё равно не должны обходить поисковые боты.

// Если в бвзе данных есть дата обновления, то записываем её в переменную $page_data->updated_at = '2021-03-14 01:05:03';
// Проверяем сразу дату изменения, так как если не нужно генерировать страницу, то и ресурсы тратить не надо
if(isset($page_data->updated_at)){
    $LastModified_unix = strtotime($page_data->updated_at);
    // Даже если известна точная дата последнего изменения страницы, то всё равно можно раз в месяц выдавать 200 ответ сервера для переиндексации. 
    // Вдруг сам шаблон шаблон за это время изменился или ещё что-то

    $wsdday = date("d",$LastModified_unix);
    if($wsdday > 28) $wsdday = 28;
    // mktime(часы, минуты, секунды, месяц, день, год)
    $wstest = mktime(date("H",$LastModified_unix), date("i",$LastModified_unix), date("s",$LastModified_unix), date("m"), $wsdday, date("Y"));
    // 15-ое число текущего месяца
    // Если сформированная дата меньше реальной, то конечно отображаем реальную как есть, а если больше, то используем её
    if($wstest > $LastModified_unix){
        if($wstest < time()){
            // если сформированная дата уже наступила
            $LastModified_unix = $wstest;
        }
        else {
            // если сформированная время больше текущей, то получаем такую-же дету месяц назад
            $wsdm = date("m") - 1;
            $wsdy = date("Y");
            if($wsdm == 0){
                $wsdm = 12;
                --$wsdy;
            }
            $wstest = mktime(date("H",$LastModified_unix), date("i",$LastModified_unix), date("s",$LastModified_unix), $wsdm, $wsdday, $wsdy);
            if($wstest > $LastModified_unix){
                $LastModified_unix = $wstest;
            }
        }
    }
}
else {
    // Формируем каждый понедельник новую дату
    // Имитируя еженедельное обновление
    // mktime(часы, минуты, секунды, месяц, день, год)
    $wstest = mktime(date("m"), date("d"), date("d")+date("m"), date("m"), date("d"), date("Y"));
    $wstoday = getdate();
    if($wstoday['wday'] == 0){
        // если сформированная время больше текущей, то отнимаем 7 дней, 7 минут и 7 секунд
        if(time() < $wstest){
            // если сформированная время больше текущей, то отнимаем 7 дней, 7 минут и 7 секунд
            $LastModified_unix = $wstest - (7*86400+7*60+7);
        }
        else {
            $LastModified_unix = $wstest;
        }
    }
    else {
        $LastModified_unix = $wstest - ($wstoday['wday']*86400+$wstoday['wday']*60+$wstoday['wday']);
    }
}
$LastModified = gmdate("D, d M Y H:i:s \G\M\T", $LastModified_unix);
$IfModifiedSince = false;
if (isset($_ENV['HTTP_IF_MODIFIED_SINCE']))
    $IfModifiedSince = strtotime(substr($_ENV['HTTP_IF_MODIFIED_SINCE'], 5));
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']))
    $IfModifiedSince = strtotime(substr($_SERVER['HTTP_IF_MODIFIED_SINCE'], 5));
if ($IfModifiedSince && $IfModifiedSince >= $LastModified_unix) {
    header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
    exit;
}
header('Last-Modified: '. $LastModified);

@aktaumag
Copy link
Author

aktaumag commented Mar 22, 2021

DLE

Почти в самое начало engine/modules/show.full.php, но когда уже получили переменную $row

if( strtotime($row['date']) > $_TIME || strtotime($row['date']) > $row['editdate']) {
	$LastModified_unix = strtotime($row['date']);
}
elseif(isset($row['editdate']) && $row['editdate'] != 0){
	$LastModified_unix = $row['editdate']; // тут дата сразу в unix формате
}
else {
	$LastModified_unix = strtotime($row['date']);
}

В самое начало engine/modules/main.php

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//// wSEO START Michael Nossov // add: Сразу обрабатываю даты последнего изменения, так как может и нет смысла выводить всю страницу и нужно отдать код 304

if(isset($dle_module) && ($dle_module == "main" || $dle_module == "cat" || $dle_module == "catalog" || $dle_module == "showfull")){
	// Применяем эти настройки только к главной, категориям, новостям (чтобы не затрагивать страницы поиска, избранного, ЛК и прочие)
	// Текущее время по гринвичу gmmktime(часы, минуты, секунды, месяц, день, год)
	$wsnaw = gmmktime(gmdate("H"), gmdate("i"), gmdate("s"), gmdate("m"), gmdate("d"), gmdate("Y"));
	
	if(!isset($LastModified_unix)){
		// Если это не статья, то у страницы нет даты изменения и поэтому генерируем сами
		// Для главной имитируем обновление каждый час
		// Для других страниц имитируем обновление каждый день
		
		if ( $dle_module == "main") {
			// если это главная
			// Формируем каждый час новую дату
			$LastModified_unix = gmmktime(gmdate("H"), gmdate("d"), gmdate("d")+gmdate("m"), gmdate("m"), gmdate("d"), gmdate("Y"));
			// Задаём время обновления
			$wsuptimer = 3600;
		}
		else{
			// Формируем каждый день новую дату
			// Имитируя ежедневное обновление
			$LastModified_unix = gmmktime(gmdate("m"), gmdate("d"), gmdate("d")+gmdate("m"), gmdate("m"), gmdate("d"), gmdate("Y"));
			// Задаём время обновления
			$wsuptimer = 86400;
		}
		
		if($wsnaw < $LastModified_unix){
			// если сформированная время больше текущей, то отнимаем нужное время и генерируем дату так, как она была бы сгенерирована в то время
			$wstest = $LastModified_unix - $wsuptimer;
			$LastModified_unix = gmmktime(gmdate("H",$wstest), gmdate("d",$wstest), gmdate("d",$wstest)+gmdate("m",$wstest), gmdate("m",$wstest), gmdate("d",$wstest), gmdate("Y",$wstest));
		}
		
	}
	else {
		// Если это статья у которой известна дата изменения/создания
		// Отдаём её на обновление каждый месяц
		
		$wsdday = gmdate("d",$LastModified_unix);
		if($wsdday > 28) $wsdday = 28; // дата для удобства, так как 28 день есть во всех месяцах
		// Формируем 28-ое число текущего месяца
		$wstemp = gmmktime(gmdate("H",$LastModified_unix), gmdate("i",$LastModified_unix), gmdate("s",$LastModified_unix), gmdate("m"), $wsdday, gmdate("Y"));
		// Если сформированная дата меньше реальной, то конечно отображаем реальную как есть, а если больше, то используем её
		if($wstemp > $LastModified_unix){
			if($wstemp < $wsnaw){
				// если сформированная дата уже наступила
				$LastModified_unix = $wstemp;
			}
			else {
				// если сформированная дата больше текущей, то получаем такую-же дету месяц назад
				$wsdm = gmdate("m") - 1;
				$wsdy = gmdate("Y");
				if($wsdm == 0){
					$wsdm = 12; // Если прошлый месяц получился 0, значит это был декабрь
					--$wsdy; // значит ещё и год отнять надо
				}
				$wstemp = gmmktime(gmdate("H",$LastModified_unix), gmdate("i",$LastModified_unix), gmdate("s",$LastModified_unix), $wsdm, $wsdday, $wsdy);
				if($wstemp > $LastModified_unix){
					$LastModified_unix = $wstemp;
				}
			}
		}
		
	}
	
	// Бывают ситуации, когда дата создания статьи указана больше, чем текущая. Это делается, когда статьи пишутся заранее... они пока не отображаются
	// Поэтому на всякий случай делаю сравнение дат. Выводим заголовки только для опубликованных статей с датой меньше текущей
	if($wsnaw > $LastModified_unix){
		$LastModified = gmdate("D, d M Y H:i:s \G\M\T", $LastModified_unix);
		$IfModifiedSince = false;
		if (isset($_ENV['HTTP_IF_MODIFIED_SINCE']))
			$IfModifiedSince = strtotime(substr($_ENV['HTTP_IF_MODIFIED_SINCE'], 5));
		if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']))
			$IfModifiedSince = strtotime(substr($_SERVER['HTTP_IF_MODIFIED_SINCE'], 5));
		if ($IfModifiedSince && $IfModifiedSince >= $LastModified_unix) {
			header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
			die();
		}
		header('Last-Modified: '. $LastModified);
	}
}
//// wSEO END
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

@aktaumag
Copy link
Author

aktaumag commented Mar 28, 2021

CMS Bitrix

В файле /bitrix/modules/main/lib/httpresponse.php находим функцию public function writeHeaders()

                if($this->lastModified !== null)
		{
			// START wSEO.kz
			// В основном это касается товаров.
			// Если мы знаем дату последнего изменения, то всё равно Отдаём её на обновление каждый месяц
			// Текущее время по гринвичу gmmktime(часы, минуты, секунды, месяц, день, год)
			$wsnaw = gmmktime(gmdate("H"), gmdate("i"), gmdate("s"), gmdate("m"), gmdate("d"), gmdate("Y"));
			
			$LastModified_unix = $this->lastModified->getTimestamp();
			$wsdday = gmdate("d",$LastModified_unix);
			if($wsdday > 28) $wsdday = 28; // дата для удобства, так как 28 день есть во всех месяцах
			// Формируем 28-ое число текущего месяца
			$wstemp = gmmktime(gmdate("H",$LastModified_unix), gmdate("i",$LastModified_unix), gmdate("s",$LastModified_unix), gmdate("m"), $wsdday, gmdate("Y"));
			// Если сформированная дата меньше реальной, то конечно отображаем реальную как есть, а если больше, то используем её
			if($wstemp > $LastModified_unix){
				if($wstemp < $wsnaw){
					// если сформированная дата уже наступила
					$LastModified_unix = $wstemp;
				}
				else {
					// если сформированная дата больше текущей, то получаем такую-же дету месяц назад
					$wsdm = gmdate("m") - 1;
					$wsdy = gmdate("Y");
					if($wsdm == 0){
						$wsdm = 12; // Если прошлый месяц получился 0, значит это был декабрь
						--$wsdy; // значит ещё и год отнять надо
					}
					$wstemp = gmmktime(gmdate("H",$LastModified_unix), gmdate("i",$LastModified_unix), gmdate("s",$LastModified_unix), $wsdm, $wsdday, $wsdy);
					if($wstemp > $LastModified_unix){
						$LastModified_unix = $wstemp;
					}
				}
			}
			// Бывают ситуации, когда дата создания статьи указана больше, чем текущая. Это делается, когда статьи пишутся заранее... они пока не отображаются
			// Поэтому на всякий случай делаю сравнение дат. Выводим заголовки только для опубликованных статей с датой меньше текущей
			if($wsnaw >= $LastModified_unix){
				$LastModified = gmdate("D, d M Y H:i:s \G\M\T", $LastModified_unix);
				$IfModifiedSince = false;
				if (isset($_ENV['HTTP_IF_MODIFIED_SINCE']))
					$IfModifiedSince = strtotime(substr($_ENV['HTTP_IF_MODIFIED_SINCE'], 5));
				if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']))
					$IfModifiedSince = strtotime(substr($_SERVER['HTTP_IF_MODIFIED_SINCE'], 5));
				if ($IfModifiedSince && $IfModifiedSince >= $LastModified_unix) {
					header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
					die();
				}
				header('Last-Modified: '. $LastModified);
				$this->flushHeader(array("Last-Modified", $LastModified));
			}
			// wSEO.kz  Закрыл старый вывод заголовка
			//$this->flushHeader(array("Last-Modified", gmdate("D, d M Y H:i:s", $this->lastModified->getTimestamp()) . " GMT"));
			// END wSEO.kz
		}

В файле header.php от нашего шаблона прописываем в самом начале

//	use \Bitrix\Conversion\Internals\MobileDetect;
	
    if(!CSite::InDir('/product/')){
	    // У продуктов настроены заголовки Last-Modified и If-Modified-Since
        // Поэтому исключаем их из этого обработчика
	    $wstt = 'na';
	    
//	    $detect = new MobileDetect;
//	    $wsua = $detect->getUserAgent();
//	    // Сами заголовки прописываем только для ботов
//	    if (preg_match('/bot|LastModBot|crawl|curl|dataprovider|search|get|spider|find|java|majesticsEO|google|yahoo|teoma|contaxe|yandex|libwww-perl|facebookexternalhit/i', $_SERVER['HTTP_USER_AGENT'])) {
//		    // is bot
//
//	    }
            // Текущее время по гринвичу gmmktime(часы, минуты, секунды, месяц, день, год)
		    $wsnaw = gmmktime(gmdate("H"), gmdate("i"), gmdate("s"), gmdate("m"), gmdate("d"), gmdate("Y"));
		
		    if(CSite::InDir('/catalog/') || $APPLICATION->GetCurPage(false) == '/') {
			    $wstt = 'catalog or main';
			    // если это главная или каталог
			    // Формируем каждый час новую дату
			    $LastModified_unix = gmmktime(gmdate("H"), gmdate("d"), gmdate("d")+gmdate("m"), gmdate("m"), gmdate("d"), gmdate("Y"));
			    // Задаём время обновления
			    $wsuptimer = 3600;
		    }
		    else {
			    $wstt = 'not catalog and not main and not product';
			    // Формируем каждый день новую дату
			    // Имитируя ежедневное обновление
			    $LastModified_unix = gmmktime(gmdate("m"), gmdate("d"), gmdate("d")+gmdate("m"), gmdate("m"), gmdate("d"), gmdate("Y"));
			    // Задаём время обновления
			    $wsuptimer = 86400;
		    }
		
		    if($wsnaw < $LastModified_unix){
			    // если сформированная время больше текущей, то отнимаем нужное время и генерируем дату так, как она была бы сгенерирована в то время
			    $wstest = $LastModified_unix - $wsuptimer;
			    $LastModified_unix = gmmktime(gmdate("H",$wstest), gmdate("d",$wstest), gmdate("d",$wstest)+gmdate("m",$wstest), gmdate("m",$wstest), gmdate("d",$wstest), gmdate("Y",$wstest));
		    }
		    if($wsnaw >= $LastModified_unix){
			    $LastModified = gmdate("D, d M Y H:i:s \G\M\T", $LastModified_unix);
			    $IfModifiedSince = false;
			    if (isset($_ENV['HTTP_IF_MODIFIED_SINCE']))
				    $IfModifiedSince = strtotime(substr($_ENV['HTTP_IF_MODIFIED_SINCE'], 5));
			    if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']))
				    $IfModifiedSince = strtotime(substr($_SERVER['HTTP_IF_MODIFIED_SINCE'], 5));
			    if ($IfModifiedSince && $IfModifiedSince >= $LastModified_unix) {
				    header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
				    die();
			    }
			    header('Last-Modified: '. $LastModified);
		    }
		
    }
    else {
	    $wstt = 'product';
    }

@aktaumag
Copy link
Author

aktaumag commented Jan 21, 2022

Если это хостинг nic.ru (RU-CENTER), то техподдержка не помогает даже для статических файлов, но можно перевести сервер в ручной режим управления и самому настроить конфигурацию Nginx.
Инструкция — https://www.nic.ru/help/nastrojka-sajta-kodirovka-perenapravlenie-logi-i-dr_6760.html#p6

Ищем в файле код:

 # Static files location
    location ~* ^.+\.(jpg|jpeg|gif|swf|png|ico|mp3|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|avi|ppt|txt|tar|mid|midi|wav|bmp|rtf|wmv|mpeg|mpg|mp4|m4a|spx|ogx|ogv|oga|webm|weba|ogg|tbz|js|7z|woff|svg|iso)$ {
        root   /home/login/domen.com/docs;
        access_log  /var/log/domen.com.access_log  combined;
        error_page 404 = @fallback;
        log_not_found off;
        accel_htaccess_switch on;
    }

Добавляем в список локации woff2 и прочие недостающие форматы
И добавляем туда if_modified_since before; и заодно expires 365d; для кеша в браузере.
Нужно чтоб получилось так:

 # Static files location
    location ~* ^.+\.(jpg|jpeg|gif|swf|png|avif|webp|ico|mp3|css|zip|tgz|gz|rar|bz2|doc|docx|xls|xlsx|exe|pdf|avi|ppt|txt|tar|mid|midi|wav|bmp|rtf|wmv|mpeg|mpg|mp4|m4a|spx|ogx|ogv|oga|webm|weba|ogg|tbz|js|7z|woff|woff2|ttf|svg|iso)$ {
        root   /home/login/domen.com/docs;
        access_log  /var/log/domen.com.access_log  combined;
        error_page 404 = @fallback;
        log_not_found off;
        accel_htaccess_switch on;
        if_modified_since before;
        expires 365d;
    }

Пример пути к файлу настроек:
/etc/nginx/vhosts/sites-enabled/site.ru.site.conf
/home/login/etc/nginx/site.ru.conf
/home/login/etc/nginx/sites-enabled/site.ru.site.conf

После внесения изменений в конфигурационные файлы Nginx достаточно перезапустить только Nginx, а не весь сервер.
Команда:
systemctl restart nginx

@aktaumag
Copy link
Author

Плагин для добавления заголовков к постам Wordpress: https://wordpress.org/plugins/last-modified-and-if-modified-since-headers/

@aktaumag
Copy link
Author

aktaumag commented May 16, 2023

Заметка

Чтобы в PHP получить заголовок запроса
в .htaccess прописываем правило

<IfModule mod_rewrite.c>
RewriteEngine On

RewriteRule .* - [E=HTTP_IF_MODIFIED_SINCE:%{HTTP:If-Modified-Since}]
RewriteRule .* - [E=HTTP_IF_NONE_MATCH:%{HTTP:If-None-Match}]
</IfModule>

А потом уже в самом PHP получаем значения переданные в запросе и возвращаем либо сайт, либо 304 ответ
$_SERVER['HTTP_IF_MODIFIED_SINCE'] и $_SERVER['HTTP_IF_NONE_MATCH']

Примерный пример (чужой код)

function caching_headers ($file, $timestamp) {
    $gmt_mtime = gmdate('r', $timestamp);
    header('ETag: "'.md5($timestamp.$file).'"');
    header('Last-Modified: '.$gmt_mtime);
    header('Cache-Control: public');

    if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) || isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
        if ($_SERVER['HTTP_IF_MODIFIED_SINCE'] == $gmt_mtime || str_replace('"', '', stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])) == md5($timestamp.$file)) {
            header('HTTP/1.1 304 Not Modified');
            exit();
        }
    }
}

caching_headers ($_SERVER['SCRIPT_FILENAME'], filemtime($_SERVER['SCRIPT_FILENAME']));

@aktaumag
Copy link
Author

aktaumag commented Jul 10, 2023

Письмо хостеру или сисадмину (переделанное).

Добрый день!
Домен: (указать домен)

Проверяю логотип: (ссылка на логотип)
На этом ресурсе: https://last-modified.com/ru/if-modified-since.html
Получаем ошибку: (скриншот)

Для исправления ситуации в конфигурации Nginx нужно прописать настройку: if_modified_since before; для всех статических файлов.

Должно получиться примерно так:

    # Static files location
    location ~* ^.+\.(jpg|jpeg|gif|swf|png|avif|webp|ico|mp3|css|zip|tgz|gz|rar|bz2|doc|docx|xls|xlsx|exe|pdf|avi|ppt|txt|tar|mid|midi|wav|bmp|rtf|wmv|mpeg|mpg|mp4|m4a|spx|ogx|ogv|oga|webm|weba|ogg|tbz|js|7z|woff|woff2|ttf|svg|iso)$ {
        # ... тут могут быть ещё и другие настройки для статических файлов
        if_modified_since before;
    }

Можете ли вы это сделать?
Или можете подсказать где/как я могу самостоятельно прописать эту настройку?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment