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

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

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

Спецификация 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
    1
    2
    3
    4
    # app/config/config.yml
    framework:
        # ...
        esi: { enabled: true }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    <!-- app/config/config.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/symfony"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:framework="http://symfony.com/schema/dic/symfony"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/symfony
            http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
    
        <framework:config>
            <!-- ... -->
            <framework:esi enabled="true" />
        </framework:config>
    </container>
    
  • PHP
    1
    2
    3
    4
    5
    // app/config/config.php
    $container->loadFromExtension('framework', array(
        // ...
        'esi' => array('enabled' => true),
    ));
    

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// src/AppBundle/Controller/DefaultController.php

// ...
class DefaultController extends Controller
{
    public function aboutAction()
    {
        $response = $this->render('static/about.html.twig');
        // установить общий максимальный возраст, который также помечает ответ, как публичный
        $response->setSharedMaxAge(600);

        return $response;
    }
}

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

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

  • Twig
    1
    2
    3
    4
    5
    6
    7
    {# app/Resources/views/static/about.html.twig #}
    
    {# вы можете использовать ссылку на контроллер #}
    {{ render_esi(controller('AppBundle:News:latest', { 'maxPerPage': 5 })) }}
    
    {# ... или на URL #}
    {{ render_esi(url('latest_news', { 'maxPerPage': 5 })) }}
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!-- app/Resources/views/static/about.html.php -->
    
    <!-- вы можете использовать ссылку на контроллер -->
    <?php echo $view['actions']->render(
        new Symfony\Component\HttpKernel\Controller\ControllerReference(
            'AppBundle:News:latest',
            array('maxPerPage' => 5)
        ),
        array('strategy' => 'esi')
    ) ?>
    
    <!-- ... или на URL -->
    <?php echo $view['actions']->render(
        $view['router']->path(
            'latest_news',
            array('maxPerPage' => 5)
        ),
        array('strategy' => 'esi')
    ) ?>
    

Используя рендерер 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 через другую спецификацию Akamai, которая поддерживается сразу же после установки Symfony её обратным прокси.

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

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

// ...
class NewsController extends Controller
{
    public function latestAction($maxPerPage)
    {
        // ...
        $response->setSharedMaxAge(60);

        return $response;
    }
}

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

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

  • YAML
    1
    2
    3
    4
    # app/config/config.yml
    framework:
        # ...
        fragments: { path: /_fragment }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    <!-- app/config/config.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:framework="http://symfony.com/schema/dic/symfony"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/symfony
            http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
    
        <!-- ... -->
        <framework:config>
            <framework:fragment path="/_fragment" />
        </framework:config>
    </container>
    
  • PHP
    1
    2
    3
    4
    5
    // app/config/config.php
    $container->loadFromExtension('framework', array(
        // ...
        'fragments' => array('path' => '/_fragment'),
    ));
    

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

Caution

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

Note

Когда вы начнёте использовать ESI, не забывайте всегда использовать директиву s-maxage вместо max-age. Так как браузер получает только агрегированный ресурс, он не знает о субкомпонентах, и поэтому будет повиноваться директиве max-age и кешировать страницу целиком. А вам это не нужно.

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

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

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.