Работа с включениями крайней стороны

Дата обновления перевода 2023-01-16

Работа с включениями крайней стороны

Шлюзовые кеши - это отличный способ повысить производительность вашего вебсайта. Но они имеют одно ограничение: они могут кешировать только страницу целиком. Если ваши страницы содержат динамические секции, такие как имя пользователя или корзину покупок, то вам не повезло. К счастью, Symfony предоставляет решение для таких случаев, основанное на технологии под названием ESI, или Включения крайней стороны. Акамаи написал эту спецификацию в 2001 году, и она позволяет определённым частям страницы иметь другие кеширующие стратегии, чем главная страница.

Спецификация ESI описывает теги, которые вы можете встраивать в ваши страницы, чтобы коммуницировать со шлюзовым кешем. Только один тег реализован в Symfony, include, так как он единственный полезен вне контекста Akamai:

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html>
    <body>
        <!-- ... некоторое содержание -->

        <!-- Встроить содержание другой страницы здесь -->
        <esi:include src="http://..." />

        <!-- ... больше содержания -->
    </body>
</html>

Note

Отметьте в примере, что каждый тег ESI требует полностью квалифицированного URL. ESI-тег представляет фрагмент страницы, который можно получить через заданный URL.

Когда обрабатывается запрос, кеширующий шлюз вызывает целую страницу из кеша или запрашивает её из приложения выходного буфера. Если ответсодержит один или более ESI-тегов, то они обрабатываются таким же образом. Другими словами, кеширующий шлюз либо извлекает включённый фрагмент страницы из его кеша, или запрашивает фрагмент страницы из приложения выходного буфера опять. Когда будут разрешены все ESI-теги, кеширующий шлюз объеиняет каждый из них с главной страницей и отправляет окончательное содержимое клиенту.

Всё это происходит прозрачно на уровне кеширующего шлюза (т.е. вне вашего приложения). Как вы увидите, если вы решите воспользоваться преимуществами ESI-тегов, Symfony сделает професс включения их почти не требущим усилий.

Использование ESI в Symfony

Для начала, чтобы использовать ESI, включите их в конфигурации вашего приложения:

  • YAML
  • XML
  • PHP
1
2
3
4
# config/packages/framework.yaml
framework:
    # ...
    esi: true

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Controller/DefaultController.php
namespace App\Controller;

// ...
class DefaultController extends AbstractController
{
    public function about()
    {
        $response = $this->render('static/about.html.twig');
        $response->setPublic();
        $response->setMaxAge(600);

        return $response;
    }
}

В этом примере, ответ помечен, как публичный, чтобы сделать всю страницу кешируемой для всех запросов, с жизненным циклом в десять минут. Далее, включите новостной тикер в шаблон, встроив действие. Это делается через помощник render() (чтобы узнать больше, прочтите, как встраивать контроллеры в шаблоны ).

Так как встроенное содержимое поступает с другой страницы (или контроллера, раз на то пошло), Symfony использует стандартного помощника render, чтобы сконфигурировать ESI-теги:

1
2
3
4
5
6
7
{# templates/static/about.html.twig #}

{# вы можете использовать ссылку на контроллер #}
{{ render_esi(controller('App\Controller\NewsController::latest', { 'maxPerPage': 5 })) }}

{# ... или на URL #}
{{ render_esi(url('latest_news', { 'maxPerPage': 5 })) }}

Используя рендерер esi (через функцию Twig render_esi()), вы сообщаете Symfony, что действие должно быть отображено, как ESI-тег. Вы можете недоумевать, почему вам может захотеться использовать помощника вместо того, чтобы просто написать ESI-тег самостоятельно. Это потому, что использование помощника заставляет ваше приложение работать ровно, даже если не установлен кеширующий шлюз.

Tip

Как вы увидите ниже, переменная maxPerPage, которую вы передаёте, доступна в качестве аргумента вашего контроллера (т.е. $maxPerPage). Переменные, переданные через render_esi также становятся частью ключа кеша, чтобы у вас были уникальные кеши для каждой комбинации переменных и значений.

При использовании функции render() по умолчанию (или установке рендерера как inline), Symfony объединяет включённое содержание страницы с главной страницей до отправки ответа клиенту. Но если вы используете рендерер esi (т.е. вызываете render_esi()) и если Symfony определяет, что она имеет дело со шлюзовым кешем, поддерживающим ESI, она сгенерирует тег включения ESI. Но если шлюзового кеша нет, или если он не поддерживает ESI, то Symfony просто объединит включённое содержание страницы с главной, так же, как было бы, если бы вы использовали render().

Note

Symfony считает, что кеш шлюза поддерживает ESI, если его запрос включает в себя HTTP-заголовок Surrogate-Capability, и значение этого заголовка содержит где-либо строку ESI/1.0.

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

  • Attributes
  • PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/Controller/NewsController.php
namespace App\Controller;

use Symfony\Component\HttpKernel\Attribute\Cache;
// ...

class NewsController extends AbstractController
{
    #[Cache(smaxage: 60)]
    public function latest(int $maxPerPage): Response
    {
        // ...
    }
}

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

Составив весь вышенаписанный код, с ESI, кеш всей страницы будет валиден в течение 600 секунд, но кеш компонента новостей будет длиться только 60 секунд.

При использовании ссылки на контроллер, ESI-тег должен ссылаться на встроенное действие, как доступный URL, чтобы кеширующий шлюз могу извлечь его независимо от остальной страницы. Symfony заботится о том, чтобы сгенерировать уникальный URL для любой ссылки контроллера, может правильно проложить маршруты, благодаря FragmentListener, который должен быть включен в вашу конфигурацию:

  • YAML
  • XML
  • PHP
1
2
3
4
# config/packages/framework.yaml
framework:
    # ...
    fragments: { path: /_fragment }

Одним замечательным преимуществом ESI-рендерера является то, что вы можете сделать ваши приложения настолько динамичными, насколько это нужно, и одновременно с этим, обращаться к приложению настолько редко, насколько это возможно.

Caution

Слушатель фрагментов отвечает только на подписанные запросы. Запросы подписываются только при использовании рендерера фрагмента и функции Twig render_esi.

Помощник render_esi поддерживает две другие полезные опции:

alt
Используется как атрибут alt в ESI-теге, который позволяет вам указывать альтернативный URL для использования, если нельзя найти src.
ignore_errors
Если установлен как "true", к ESI будет добавлен атрибут onerror со значением continue, обозначающим, что в случае неудачи, кеширующий шлюз просто тихо удалит ESI-тег.