Композитный режим Битрикса: подключение через D7-роутинг

Композитный режим Битрикса: подключение через D7-роутинг

Композитный режим — это самый простой способ ускорить сайт на Битриксе: гостям отдаётся готовая страница-картинка в виде HTML, а PHP и база данных при этом почти не работают. Разбираю, как это устроено, как включить и почему настройка зависит от того, как у вас сделаны страницы — по файлам или через роутер.

Эта статья — продолжение прошлой про D7-роутинг, где я рассказывал, как отказаться от физических index.php и завести все маршруты через один файл правил. Если ваш сайт устроен классически (по файлам), хватит и статьи-инструкции ниже — а если через роутер, понадобятся обе: сначала про то, как устроены страницы, потом — как на них завести композит.

Что такое композитный режим и зачем он вам

Представьте, что каждый гость сайта заставляет сервер «готовить блюдо с нуля»: запустить PHP, сходить в базу, собрать страницу. Если на сайте написана статья, она у всех одинаковая — зачем готовить её заново для каждого зашедшего?

Композитный режим работает так: страницу готовят один раз, а результат кладут в файл. Дальше гость получает уже готовый файл, почти как обычную картинку или PDF. Серверу не нужно ни PHP, ни базы — страница открывается мгновенно.

Что это вам даёт:

  • сайт открывается заметно быстрее;
  • меньше нагрузка на сервер и базу — он не падает под наплывом;
  • поисковикам нравится быстрый сайт.

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

  • Неавторизованные посетители («гости») — главная аудитория композита. Им кеш отдаётся всегда, и именно ради них всё затевается.
  • Администраторы (те, у кого открыта админ-панель) — кеш им не отдаётся никогда. С панелью страница всегда собирается «живой». Это сделано специально: увидеть панель и правки «на лету» можно только на живой странице.
  • Авторизованные пользователи (зарегистрированные, но без админ-панели) — вот здесь всё зависит от настройки. По умолчанию кеш им тоже не показывается — страница собирается заново, ведь у каждого могут быть свои данные (корзина, личный кабинет). Но в настройках композита есть вкладка «Группы», где можно явно разрешить композит для нужных групп пользователей. Тогда эти авторизованные тоже будут получать кеш (первый заход — живая сборка, повторный — из кеша).

Проще говоря: «только для гостей» — это поведение по умолчанию, а не жёсткое правило. Композит можно включить и для своих авторизованных пользователей. Но включать его для всех подряд без разбора нельзя: если страница показывает персональные данные, а вы закешировали её целиком без динамических областей — данные одного пользователя может увидеть другой. Это классическая ошибка и лазейка для утечки информации.

Полный цикл: как страница попадает в кеш

Смотрим на жизнь страницы по шагам:

  1. Гость открывает страницу — например, статью блога.
  2. PHP собирает страницу и записывает готовый результат в файл кеша.
  3. Следующий гость страницу уже не «готовит» — веб-сервер отдаёт готовый файл.
  4. Через некоторое время (обычно около двух минут) кеш сам обновляется — страницу готовят заново, чтобы изменения не висели вечно. При правке инфоблока кеш сбрасывается автоматически.
  5. Администраторы с админ-панелью кеш не видят никогда, а авторизованным пользователям его отдают только если это разрешено настройкой «Группы» — для остальных страница собирается заново.

Имя файла кеша строится по адресу страницы: примерно так .../bitrix/html_pages/<хост><адрес>index@<параметры>.html. То есть у каждой страницы свой кеш. Это пригодится, когда будем настраивать отдачу.

Почему настройка зависит от того, как устроены страницы

Всё сложное в композите — это не сам механизм, а то, кто и когда кладёт страницу в кеш. А это зависит от того, как на сайте устроены страницы. Их бывает два вида.

Вариант 1. Классика: каждая страница — отдельный файл

Старый, привычный способ. Каждому адресу соответствует свой файл index.php, который сам подключает шаблон и вызывает компонент. Здесь весь механизм композита работает сам, «из коробки».

Что это значит на практике:

  • включить композит можно одной галочкой в админке;
  • ядро само пишет кеш и само понимает, кому его отдавать;
  • дополнительных плясок с бубном почти нет.

Вариант 2. D7-роутинг: страниц-файлов нет

Современный способ, когда все страницы описываются в одном файле правил (например, local/routes/web.php), а обрабатывает их общая точка входа — файл routing_index.php. Файлов под каждую страницу нет вообще.

Вот тут и начинается главное отличие. В классике есть специальный файл prolog.php, который «запускает» композит — даёт команду писать кеш. При D7-роутинге prolog.php не выполняется. А значит, композит сам по себе не включится — его приходится «запускать руками».

Проще говоря: в классике композит заводится галочкой в админке, а в D7-роутинге — руками в коде точки входа. Давайте посмотрим, что именно приходится делать.

Что приходится делать руками в D7-роутинге

Всё самое важное живёт в одном файле — точке входа роутера bitrix/routing_index.php, куда приходит каждый запрос сайта (в web.php правило, метаданные и вёрстка — там, в этой статье нас интересует только композит). Вот что приходится добавить в начало файла, до обработки маршрутов:


<?php
require_once($_SERVER['DOCUMENT_ROOT'].'/bitrix/modules/main/start.php');

if(file_exists($_SERVER['DOCUMENT_ROOT'].BX_PERSONAL_ROOT.'/html_pages/.enabled'))
{
	if(!defined('B_EPILOG_INCLUDED'))
		define('B_EPILOG_INCLUDED', true);

	require_once($_SERVER['DOCUMENT_ROOT'].BX_PERSONAL_ROOT
		.'/modules/main/lib/composite/responder.php');
	Bitrix\Main\Composite\Responder::respond();

	AddEventHandler('main', 'OnBeforeRestartBuffer', function () {
		if(defined('USE_HTML_STATIC_CACHE') && USE_HTML_STATIC_CACHE === true)
		{
			\Bitrix\Main\Composite\Engine::setUseHTMLCache(true);
		}
	}, 200);
}

include_once($_SERVER['DOCUMENT_ROOT']
	.'/bitrix/modules/main/include/routing_index.php');

Разберём по строкам, потому что без понимания каждая из них — просто магия:

  • Внешний if — проверяем, что композит вообще включён (рядом лежит маркер html_pages/.enabled). Если выключен — ничего из этого блока не выполняется, сайт работает как обычно.
  • B_EPILOG_INCLUDED — в классике эту константу ставит служебный файл эпилога, а при роутинге его нет. Без неё движок просто не запишет страницу в кеш.
  • Responder::respond() — это та самая команда «отдай готовый кеш или помечь текущую страницу для записи», которую в классическом входе вызывает prolog.php. Здесь мы зовём её сами. Внутри он сам определяет флаг USE_HTML_STATIC_CACHE для любого валидного запроса — это тот самый сигнал «данную страницу можно и нужно кешировать».
  • USE_HTML_STATIC_CACHE вручную ставить не нужно. Динамик-запросы самого композита (BX_ACTION_TYPE = get_dynamic) — это тоже валидные запросы, Responder их не отсеивает как AJAX и флаг определяет для них сам. Отдельный ручной define('USE_HTML_STATIC_CACHE') в этом блоке — мёртвый код: к моменту выполнения флаг уже определён, и ветка никогда не срабатывает. Важно не путать: get_dynamic распознаётся как AJAX не в Responder::isValidRequest(), а позже — в endBuffering() через Helper::isAjaxRequest().
  • Обработчик OnBeforeRestartBuffer — самое неочевидное и, по сути, главный фикс. Компонентный ответ в начале работы делает RestartBuffer(), и штатный обработчик композита в ответ выключает движок — страница так и не запишется. Наш обработчик с сортировкой 200 (выполняется позже штатного) включает движок обратно через setUseHTMLCache(true) — внутри он сам дергает setEnable(). Именно этот обработчик чинит и динамику композита: если бы после RestartBuffer() движок остался выключенным, для get_dynamic-запроса endBuffering() подменил бы ответ ошибкой buffer_restarted (именно её вы увидите в плагине «Композит» в браузере). Флаг в проверке обработчика уже определён Responder'ом, поэтому движок включается назад и корректно возвращает HTML-кеш или JSON динамических областей.

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

Как настроить отдачу кеша веб-сервером

Кеш движок записывает сам. Но чтобы гость получал готовый файл без участия PHP — а это и есть смысл композита, скорость — нужен блок правил в .htaccess. Он «перехватывает» запрос и, если в папке кеша лежит готовый файл для этого адреса, отдаёт его напрямую, не запуская PHP.

Правило проверяет, что запрос пришёл именно от «годного» гостя:

  • метод запроса — GET;
  • адрес не служебный (то, что лежит в /bitrix/, из кеша не отдаём);
  • нет признаков AJAX-запросов (X-Requested-With, bxajaxid, get_dynamic) — иначе сломается пагинация без перезагрузки страницы;
  • в адресе нет служебных параметров вроде ncc, sessid;
  • авторизация пользователя отключена от композита — наличие куки _NCC (композит запрещён) или авторизационных куки без разрешающей куки _CC означает «кеш не отдаём».

Код целиком:


RewriteCond %{REQUEST_METHOD} =GET
RewriteCond %{REQUEST_URI} !^/bitrix/
RewriteCond %{HTTP:X-Requested-With} !^XMLHttpRequest$
RewriteCond %{HTTP:BX_AJAX} ^$
RewriteCond %{HTTP:BX_ACTION_TYPE} !^get_dynamic$
RewriteCond %{QUERY_STRING} !(^|&)(ncc|sessid|bxajaxid)=
RewriteCond %{HTTP_COOKIE} !(^|;\s*)BITRIX_SM_NCC=Y(;|$)
RewriteCond %{HTTP_COOKIE} !BITRIX_SM_LOGIN=[^;\s]+
RewriteCond %{REQUEST_URI}@@%{QUERY_STRING} ^([^@]+)@@(.*)$
RewriteCond %{DOCUMENT_ROOT}/bitrix/html_pages/<хост>%1index@%2.html -f
RewriteRule ^.*$ bitrix/html_pages/<хост>%1index@%2.html [L]

Что важно понимать:

  • Имя файла кеша детерминировано: bitrix/html_pages/<хост><адрес>index@<параметры>.html. Последние два условия как раз собирают это имя из адреса и параметров запроса и проверяют, существует ли такой файл (-f).
  • Не меняйте склейку @@ на два отдельных условия. В mod_rewrite ссылки на захваченные части (%1, %2) берутся только от последнего совпавшего RewriteCond. Если «развернуть» на два условия — путь соберётся мусорным и ничего не заработает (на этом я однажды потерял много времени).
  • Промах безопасен. Файла для этого адреса нет — правило не срабатывает, и запрос спокойно уходит в PHP, где страницу соберёт и закеширует Responder. Худший случай — один запрос без ускорения.
  • Подставьте своё имя хоста вместо <хост> — это имя папки сайта в bitrix/html_pages/.

Этот блок отдачи надо ставить до того catch-all-правила, которое направляет все запросы на routing_index.php — иначе до готового файла дело не дойдёт.

Заголовки: почему плагин ругается «Bad header»

Когда страницу отдаёт PHP, он сам выставляет нужные заголовки. Но когда её отдаёт Apache напрямую — PHP не участвует, и плагин «Композит» в браузере начинает жаловаться, что страница якобы не из кеша. Лечится блоком заголовков рядом с правилами отдачи:


<IfModule mod_setenvif.c>
  <IfModule mod_headers.c>
    SetEnvIf Request_URI ^/bitrix/html_pages/ COMPOSITE_CACHE=1
    Header set X-Bitrix-Composite "Cache (200)" env=COMPOSITE_CACHE
    Header set Content-Type "text/html; charset=utf-8" env=COMPOSITE_CACHE
    Header set Expires "Fri, 07 Jun 1974 04:00:00 GMT" env=COMPOSITE_CACHE
  </IfModule>
</IfModule>

Expires здесь не украшение: без него браузер начнёт сам кешировать страницу по дате изменения и перестанет проверять свежесть — обновления контента будут доходить с задержкой.

Ошибки, на которых обжигаются чаще всего

Теперь то, что обычно не написано в документации:

  • AJAX-пагинация не должна попадать в кеш. Если её не отсечь, веб-сервер отдаст гостю целую страницу там, где скрипт ждёт маленький кусочек. Список блога просто «сломается».
  • Не делите условие mod_rewrite на две части. В этом языке ссылки на захваченные части берутся только от последнего условия, поэтому адрес и параметры ловятся одним условием через специальный разделитель — иначе ничего не заработает.
  • Файл кеша «мигает». Когда страница обновляется, файл на секунды может пропадать. Если правило вдруг не сработало — сначала проверьте, есть ли вообще файл, и только потом ищите ошибку в правилах.
  • Заголовки с подчёркиванием легко теряются. Если спереди стоит nginx, он может «съесть» заголовок BX_ACTION_TYPE до того, как его увидит Apache. Из-за этого локально динамику композита невозможно отличить от обычного запроса, и отладить её не выйдет.
  • buffer_restarted в логе — это нормально. Для сайта, где страницы собирает контроллер, такое сообщение в журнале динамики не ошибка, а особенность архитектуры. На работу сайта оно не влияет.

Как проверить, что всё работает

  1. Зайдите на сайт как гость (без входа в админку) — страница собралась, и в папке кеша появился файл.
  2. Обновите страницу ещё раз — теперь она отдаётся из кеша. Об этом скажет заголовок X-Bitrix-Composite: Cache (200) и плагин «Композит» в браузере.
  3. Войдите как администратор — вы всегда получаете живую страницу, пометки кеша нет. Это нормальное и правильное поведение.
  4. Если в настройках включили композит для авторизованных (вкладка «Группы») — проверьте и такой сценарий: залогиньтесь как обычный пользователь, зайдите дважды и убедитесь, что контент корректный и персональные данные не «перетекают» между пользователями.
  5. Проверьте AJAX-пагинацию — должен подгружаться только список, а не вся страница.
  6. Поправьте что-то в инфоблоке — до двух минут страница может показывать старую версию. Это штатно: кеш обновится сам.

Короткий чек-лист — как применить на своём сайте

Чтобы перенести этот опыт на ваш проект, пройдитесь по шагам:

  1. Включите композит в админке (Настройки → Настройки продукта → Композитный сайт). Убедитесь, что появился маркер bitrix/html_pages/.enabled.
  2. Определите тип своего сайта. Классический (по файлам index.php) — хватит шагов 1, 4, 5. На D7-роутинге — нужен ещё шаг 3.
  3. D7-роутинг: добавьте блок композита в начало bitrix/routing_index.php (код выше) и задокументируйте его — при переустановке ядра он теряется.
  4. Добавьте отдачу в .htaccess: правило перезаписи + блок заголовков (код выше), подставив свой хост вместо <хост>, и поставьте их до catch-all-правила.
  5. Проверьте по сценариям из раздела ниже: гость два захода, администратор, AJAX-пагинация, правка инфоблока.

Ключевые вещи, которые легко упустить:

  • В D7-роутинге композит не «заводится» галочкой — prolog.php не выполняется, точку входа надо донастроить руками.
  • Код интеграции живёт в bitrix/routing_index.php, которого нет в репозитории, — без документации он молча потеряется при переносе сайта.
  • AJAX исключён из кеша; администраторы с админ-панелью не должны попадать в группы композита.
  • Если композит включается и для авторизованных — вынесите их в отдельную группу на вкладке «Группы», а страницы с персональными данными либо не кешируйте, либо размечайте динамическими областями.