HTTP Cache (RUS)



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

Для многих веб приложений подобная ситуация в порядке вещей. Благодаря молниеносной быстроте Symfony2 Вы можете делать нечто действительно “полновесное”. При этом каждый запрос будет возвращаться быстро, не оказывая чрезмерной нагрузки на Ваш сервер.

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

Кэширование “на плечах гигантов”

Самый эффективный способ повысить производительность приложения – кэшировать все выходные данные страницы, а затем полностью блокировать приложение при каждом последующем запросе. Конечно же, такая схема не всегда возможна для динамичных сайтов. Но так ли это на самом деле? В этой главе мы покажем Вам как работает система кэширования Symfony2 и объясним, почему мы считаем такой способ лучшим.

Система кэширования Symfony2 полагается на простоту и мощь HTTP кэша, как это указывается в технических требованиях HTTP. Вместо повторного изобретения методологии кэширования Symfony2 пользуется стандартом, определяющим основные взаимодействия веба. Как только Вы поймёте основные модели кэширования HTTP (“валидация” и “завершение”), то будете готовы освоить систему кэширования Symfony2.

Для того, чтобы научиться кэшированию с помощью Symfony2, мы разобьём объяснение на четыре этапа:

  • Этап 1: Шлюз-кэш, или обратный прокси-сервер – это независимый уровень, который находится перед Вашим приложением. Обратный прокси-сервер кэширует ответы по мере их возвращения из Вашего приложения, отвечая на запросы кэшированными ответами перед тем, как они достигнут приложения. Symfony2 предоставляет собственный обратный прокси-сервер, однако Вы можете использовать оюбой другой сервер.
  • Этап 2: Заголовки HTTP кэша используются для взаимодействия со шлюз-кэшем, а также с другими типами кэша, между Вашим приложением и пользователем. Symfony2 предлагает удобные настройки и мощный интерфейс для взаимодействия с заголовками кэша.
  • Этап 3: HTTP “завершение” и “валидация” – две модели, использующиеся для определения “свежести” (может быть использовано кэшем повторно) или “несвежести” (должно быть заново сгенерировано приложением) содержимого кэша.
  • Этап 4: ESI (Edge Side Includes) позволяет использовать HTTP кэш для фрагментов страницы (даже вложенных фрагментов). Используя ESI, Вы можете кэшировать целую страницу за 60 минут, а встроенную боковую панель – всего за пять минут.
  • Так как HTTP кэширование не является уникальным для Symfony, многие статьи можно найти в соответствующей теме. Если Вы только знакомитесь с HTTP кэшированием, мы настоятельно рекомендуем статью Раяна Томайко (Ryan Tomayko) “Что делает кэш” (Things Caches Do). Другим исчерпывающим источником является “Учебное Пособие по Кэшу” (Cache Tutorial) Марка Ноттингхэма (Mark Nottingham).

    Использование шлюз-кэша

    При использовании HTTP, кэш полностью отделён от Вашего приложения и находится между приложением и пользователем, делающим запрос.

    Работа кэша заключается в том, чтобы принимать запросы запросы от пользователя и отправлять их приложению. Кэш также получает ответы приложения и перенаправляет их пользователю. Кэш выступает в роли посредника во взаимодействии “запрос-ответ” между пользователем и Вашим приложением.

    Попутно кэш будет сохранять ответ, который будет расценен ка “пригодный для кэширования”. При повторном запросе к одному ресурсу, кэш отправляет сохранённый ответ пользователю, полностью игнорируя приложение.

    Данный тип известен как HTTP шлюз-кэш.

    Типы кэша

    Однако шлюз-кэш не является единственным типом кэша. Фактически, заголовки HTTP кэша, отправленные Вашим приложением, поглощаются и интерпретируются в три различных типа кэша:

  • Кэш браузера: каждый браузер имеет в наличии собственный локальный кэш, используемый, в основном, для “возвращения назад” либо для изображений, либо других ценных данных. Кэш браузера является приватным, поскольку его ресурсы не используются совместно с другими пользователями.
  • Прокси-кэш: этот тип является совместно используемым. Обычно используется крупными корпорациями, а также поставщиками интернет-услуг (ISPs), чтобы сократить время ожидания и сетевой трафик.
  • Шлюз-кэш: как и прокси, этот тип также является совместно используемым, но уже со стороны сервера. Установленный сетевыми администраторами, он делает веб-сайты более расширяемыми, надёжными и эффективными.
  • Шлюз-кэш иногда может выступать в роли резервного прокси- или замещающего кэша, или даже HTTP ускорителя.

    Разница между приватным кэшем и кэшем совместного доступа становится более очевидной, когда мы говорим о кэшировании ответов с содержанием, свойственным только одному пользователю (например, информация учётной записи).

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

    Реверсный прокси-сервер Symfony2

    Symfony2 изначально содержит реверсный прокси-сервер (называемый также шлюз-кэшем), написанный на PHP. Включите его и соответствующие ответы Вашего приложения тут же начнут кэшироваться. Установка довольно проста. Каждое новое приложение Symfony2 содержит предварительно настроенное ядро кэширования (AppCache), которое заменяет собой использующееся по умолчанию (AppKernel). Ядро кэширования и является реверсным прокси-сервером.

    Чтобы включить кэширование, измените код во фронт-контроллере:

    // web/app.php
    require_once __DIR__.'/../app/bootstrap.php.cache';
    require_once __DIR__.'/../app/AppKernel.php';
    require_once __DIR__.'/../app/AppCache.php';
    use Symfony\Component\HttpFoundation\Request;
    $kernel = new AppKernel('prod', false);
    $kernel->loadClassCache();
    // wrap the default AppKernel with the AppCache one
    $kernel = new AppCache($kernel);
    $kernel->handle(Request::createFromGlobals())->send();

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

    Ядро содержит специальную функцию getLog(), которая отображает строковое отображение происходящего в слое кэша. Эта функция может быть использована в среде программирования для отладки и проверки поведения кэша:

    error_log($kernel->getLog());

    Объект AppCache имеет удобные настройки, однако Вы можете настроить объект с помощью набора опций, устанавливаемого путём подмены функции getOptions():

    // app/AppCache.php
    class AppCache extends Cache
    {
    protected function getOptions()
    {
    return array(
    'debug' => false,
    'default_ttl' => 0,
    'private_headers' => array('Authorization', 'Cookie'),
    'allow_reload' => false,
    'allow_revalidate' => false,
    'stale_while_revalidate' => 2,
    'stale_if_error' => 60,
    );
    }
    }

    Без подмены функции getOptions(), опция отладки автоматически будет использоваться для заменённого AppKernel.

    Вот список основных опций:

  • default_ttl: Количество секунд, в течение которых кэш будет рассматриваться в качестве актуального в случаях, когда ответ не содержит точной информации об актуальности. Заголовки Cache-Control или Expires переопределяют это значение (по умолчанию: 0);
  • private_headers: Набор заголовков запроса, приводящих в действие директиву Cache-Control, действующую для ответов, не содержащих точную информацию о том, являются ли данные ответы public или private. (по умолчанию: Authorization и Cookie);
  • allow_reload: Определяет возможность пользователя произвести перезагрузку кэша путём включения в запрос директивы Cache-Control «no-cache». Установите значение true для совместимости с RFC 2616 (по умолчанию: false);
  • allow_revalidate: Определяет возможность пользователя произвести повторную проверку кэша путем включения в запрос директивы Cache-Control «max-age=0″. Установите значение true для совместимости с RFC 2616 (по умолчанию: false);
  • stale_while_revalidate: Определяет количество секунд (степень детализации будет вторичной, поскольку точность Response TTL вторична) в течение которых кэш может немедленно вернуть неактуальный ответ, перепроверенный в фоновом режиме (по умолчанию: 2); это значение заменено дополнением stale-while-revalidate HTTP Cache-Control (смотрите RFC 5861);
  • stale_if_error: Определяет количество секунд (степень детализации будет вторичной) в течение которых кэш может обслуживать неактуальный ответ при внезапном возникновении ошибок (по умолчанию: 60). Это свойство заменено дополнением stale-if-error HTTP Cache-Control (смотрите RFC 5861).
  • Если для debug установлено значение true, Symfony2 автоматически добавляет заголовок X-Symfony-Cache к ответу, содержащему полезную информацию о совпадениях и несовпадениях кэша.

    Переход с реверсного прокси-сервера на другой

    Реверсный прокси-сервер Symfony2 – это прекрасное средство для использования при программировании Вашего сайта или при перемещении его на хост совместного доступа, где Вы не можете установить ничего, помимо написанного с использованием PHP кода. Однако, написанное на PHP не может сравниться в скорости с прокси, написанным на C. Поэтому мы настоятельно рекомендуем Вам, по возможности, использовать Varnish или Squid на производственных серверах. Хорошая новость заключается в том, что переключение с одного прокси сервера на другой осуществляется просто, поскольку не требует изменения кода Вашего приложения. Для начала используйте простой реверсный прокси-сервер Symfony2, а затем, когда возрастёт трафик, Вы сможете перейти на Varnish.

    Эффективность реверсного прокси-сервера Symfony2 не зависит от сложности приложения. Это потому, что ядро приложения загружается только тогда, когда к нему должен быть направлен запрос.

    Введение в HTTP кэширование

    Чтобы воспользоваться доступными уровнями кэша, Вашему приложению необходимо уведомлять о том, какие ответы пригодны для кэширования, а также правила, определяющие, когда и как кэш становится неактуальным. Это делается путём определения в ответе HTTP заголовков кэша.

    Обратите внимание на то, что “HTTP” – это язык (простой текстовый язык), используемый веб-клиентами (нпример, браузерами) и веб-серверами для взаимодействия друг с другом. Когда мы говорим о HTTP кэшировании, мы имеем в виду часть этого языка, которая позволяет пользователям и серверам обмениваться информацией, пригодной для кэширования.

    HTTP определяет четыре заголовка кэша, которые нас интересуют:

  • Cache-Control
  • Expires
  • Etag
  • Last-Modified
  • Наиболее важным и универсальным заголовком является Cache-Control, который является, по сути, набором различной кэш-информации.

    Заголовок Cache-Control

    Заголовок Cache-Control уникален, поскольку содержит не один, но несколько блоков информации о пригодности ответа для кэширования. Каждый информационный блок выделен запятой:

    Cache-Control: private, max-age=0, must-revalidate
    Cache-Control: max-age=3600, must-revalidate

    В Symfony Cache-Control имеет собственное место, дабы сделать создание этого заголовка более управляемым:

    $response = new Response();
    
    // mark the response as either public or private
    $response->setPublic();
    $response->setPrivate();
    
    // set the private or shared max age
    $response->setMaxAge(600);
    $response->setSharedMaxAge(600);
    
    // set a custom Cache-Control directive
    $response->headers->addCacheControlDirective('must-revalidate', true);

    Ответы Public и Private

    Два типа кэша, прокси- и шлюз-кэш, являются “общедоступными”, поскольку их содержимое доступно более чем одному пользователю. Даже если ответ, предназначенный для определённого пользователя, будет ошибочно сохранён кэшем совместного доступа, позже его можно будет вернуть любому количеству различных пользователей. Представьте, что информация Вашей учётной записи была кэширована и затем выводится для каждого следующего пользователя, запросившего страницу собственной учётной записи!

    Чтобы справиться с подобной ситуацией, для каждого ответа можно установить статус public или private:

  • public: ответ может быть сохранён как приватным кэшем, так и кэшем совместного доступа;
  • private: ответное сообщение, или часть его, предназначено для одного определённого пользователя и не должно сохраняться в кэше совместного доступа.
  • По умолчанию, Symfony делает каждый ответ приватным. Чтобы воспользоваться кэшем совместного доступа (например, реверсным прокси-сервером Symfony2), необходимо установить для ответа статус public.

    Безопасные функции

    HTTP кэширование работает только для “безопасных” функций (например, GET или HEAD). Понятие “безопасные” означает, что Вы никогда не меняете статус приложения на сервере во время обработки запроса (естественно, Вы можете вести учёт информации, кэшировать данные и т.д.). Из вышесказанного можно сделать два разумных вывода:

  • Никогда не меняйте статус Вашего приложения в то время, когда происходит обработка GET или HEAD запроса. Даже если Вы не используете шлюз-кэш, наличие прокси-кэша означает, что любой GET или HEAD запрос может обращаться к Вашему серверу.
  • Не надейтесь увидеть функции PUT, POST или DELETE в кэше. Они используются в случаях изменения статуса Вашего приложения (например, удаление поста в блоге). Кэширование указанных функций будет препятствовать обращению обычных запросов к серверу и приведёт к изменению Вашего приложения.
  • Правила кэширования и значения по умолчанию

    HTTP 1.1 позволяет кэшировать любую информацию по умолчанию до тех пор, пока не будет изменён заголовок Cache-Control. Фактически, большинство типов кэша бездействуют, если запросы имеют cookie, авторизационный заголовок, не используют “безопасные” функции (т.е. PUT, POST, DELETE), или в тех случаях, когда ответы содержат код изменения статуса.

    Если заголовок не выбран разработчиком, Symfony2 автоматически определяет стандартный заголовок Cache-Control для соблюдения следующих правил:

  • Если заголовок кэша не выбран (Cache-Control, Expires, ETag или Last-Modified), Cache-Control устанавливается в режим no-cache, в котором ответ не будет кэшироваться;
  • Если Cache-Control пуст (но при этом присутствует другой заголовок кэша), его значение устанавливается на private, must-revalidate;
  • Если установлена хотя бы одна директива Cache-Control, а директивы ‘public’ или private не были добавлены, Symfony2 автоматически добавляет директиву private (кроме случаев, когда установлена s-maxage).
  • HTTP “завершение” и валидация

    Спецификация HTTP подразделяется на две модели кэширования:

  • Завершающая модель позволяет Вам определить, как долго ответ должен считаться “свежим”, путём включения Cache-Control и/или Expires заголовка. Кэш, поддерживающий “завершение” не будет отправлять повторный запрос до тех пор, пока у кэшированной версии запроса не истечёт срок действия и она не станет “неактуальной”.
  • Когда страницы по-натоящему динамичны (т.е., их отображение часто меняется), Вам потребуется валидационная модель. При использовании этой модели, кэш сохраняет ответ, однако, при каждом запросе обращается к серверу, чтобы удостовериться в валидности закэшированного ответа. Приложение использует уникальный идентификатор ответов (заголовок Etag) и/или временные метки (заголовок Last-Modified) для подтверждения изменения страницы после кэширования.
  • Назначение обеих моделей сходно: полагаясь на кэш для сохранения и отображения “свежих” ответов, не допустить повторного генерирования одного и того же ответа.

    Чтение HTTP спецификации

    HTTP спецификация определяет простой, но мощный язык для взаимодействия между пользователями и серверами. В качестве веб-программиста нашу работу контролирует модель “запрос-ответ”. К сожалению, настоящий спецификационный документ – RFC 2616 – бывает сложно прочесть.

    Для переписывания RFC 2616 ведётся непрекращающаяся работа (HTTP Bis). Она ведётся не для описания новой версии HTTP, но, в основном, объясняет оригинальную спецификацию HTTP. Структура также улучшена, поскольку спецификация разбита на семь частей; всё, что касается HTTP кэширования можно найти в двух соответствующих частях (P4 – Условные запросы и P6 – кэширование: браузер- и посреднический кэш).

    Как веб-разработчики, мы настоятельно рекомендуем Вам читать спецификацию. Она доступна для понимания и достаточно мощна – десять лет спустя после создания, она по-прежнему ценится. Не смотрите на внешний вид спецификации – её содержание гораздо ценнее обложки.

    Истечение срока пригодности

    “Завершение” является наиболее эффективной и прямо направленной из двух моделей кэширования; её необходимо использовать везде, где возможно. Если ответ кэшируется с помощью “завершения”, кэш сохранит его и отобразит напрямую, без обращения к приложению, до тех пор, пока не истечёт срок актуальности ответа.

    “Завершение” может быть выполнено с помощью двух, почти идентичных, HTTP заголовков: Expires или Cache-Control.

    “Завершение” с помощью заголовка Expires

    Согласно HTTP спецификации, “поле заголовка Expires отображает дату/время, после которой ответ будет считаться неактуальным.” Заголовок Expires можно установить с помощью функции setExpires() Response. В качестве параметра потребуется привязка DateTime:

    $date = new DateTime();
    $date->modify('+600 seconds');
    $response->setExpires($date);

    В результате, HTTP заголовок будет выглядеть следующим образом:

    Expires: Thu, 01 Mar 2011 16:00:00 GMT

    Функция setExpires() автоматически конвертирует дату, согласно часовому поясу GMT, которого требует спецификация.

    Однако, для заголовка Expires существуют два ограничения. Во-первых, часы веб-сервера и кэша (например, браузера) должны быть синхронизированы. Во-вторых, в спецификации говорится: “HTTP/1.1 сервера не должны отправлять даты Expires более, чем на год вперёд.”

    “Завершение” с помощью заголовка Cache-Control

    Из-за ограничения, налагаемых на заголовок Expires, большую часть времени вместо него Вы будете использовать заголовок Cache-Control. Напомним, что Cache-Control используется для определения множества различных директив кэша. Для “завершения” существуют две директивы: max-age и s-maxage. Первая используется для всех типов кэша, вторая же учитывается типами кэша совместного доступа:

    // Sets the number of seconds after which the response
    // should no longer be considered fresh
    $response->setMaxAge(600);
     
    // Same as above but only for shared caches
    $response->setSharedMaxAge(600);

    Заголовок Cache-Control приобретёт следующий формат (он может иметь дополнительные директивы):

    Cache-Control: max-age=600, s-maxage=600

    Валидация

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

    Валидационная модель направлена на разрешение этого вопроса. При использовании этой модели кэш продолжает хранить ответы. Разница заключается в том, что при каждом запросе кэш обращается к приложению за подтверждением валидности ответа. Если кэш всё ещё актуален, Ваше приложение выдаст статусный код 304 без содержания. Для кэша это служит сигналом о выдаче хранящегося в нём ответа.

    Используя валидационную модель, Вы, в основном, экономите пропускную способность, поскольку отображение не отсылается одному клиенту дважды (вместо этого выдаётся ответ 304). Однако, если Вы хорошо продумали Ваше приложение, можно использовать минимум данных для выдачи ответа 304, а также сэкономить ресурс CPU (ниже приведён пример реализации).

    Статусный код 304 означает: “Без Изменений” («Not Modified»). Этот код важен, поскольку в ответ на запрос не выдаётся никакого настоящего содержания. Вместо этого выдаётся “легковесный” набор директив, указывающих кэшу на необходимость использования сохранённой в нём версии.

    Как и в случае с “завершением”, для валидационной модели существуют два различных HTTP заголовка: ETag и Last-Modified.

    Валидация с помощью заголовка Etag

    Заголовок ETag является строчным (который называют “объектным тэгом” («entity-tag»)), который однозначно идентифицирует отображение целевого ресурса. Он полностью генерируется и устанавливается Вашим приложением, поэтому если, к примеру, ресурс /about хранящийся в кэш, будет актуальным, то он и отобразится Вашим приложением. ETag является, по сути, отпечатком пальца и используется для быстрого сравнения двух различных версий ресурса, чтобы удостовериться в их сходстве. Как и отпечатки пальцев, каждый ETag должен быть уникальным при каждом отображении одного и того же ресурса.

    Давайте рассмотрим пример простого применения генерирования ETag в качестве md5 содержания:

    public function indexAction()
    {
    $response = $this->render('MyBundle:Main:index.html.twig');
    $response->setETag(md5($response->getContent()));
    $response->isNotModified($this->getRequest());
    
    return $response;
    }

    Функция Response::isNotModified() сравнивает Etag, отправленный запросом (Request) с одним значением, для ответа (Response). Если оба совпадают, функция автоматически устанавливает статусный код 304 для Response.

    Данный алгоритм достаточно прост и очень обобщён. Однако, Вам необходимо будет создать Response целиком перед тем, как сделать выкладку ETag. Иными словами, алгоритм экономит пропускную способность, но не циклы процессора.

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

    Symfony2 также поддерживает редуцированные Etags путём добавления true в качестве второго параметра для функции setETag().

    Валидация с помощью заголовка Last-Modified

    Заголовок Last-Modified представляет собой вторую форму валидации. Согласно HTTP спецификации: “Поле заголовка Last-Modified отображает дату и время, когда исходный сервер полагает, что отображение было обновлено.” Иными словами, приложение определяет, было ли обновлено содержимое кэша, основываясь на информации о его обновлении с того времени, как ответ был закеширован.

    Например, Вы можете использовать данные последнего обновления для всех объектов, необходимых для того, чтобы сделать выборку отображения ресурса, в качестве значения для заголовка Last-Modified:

    public function showAction($articleSlug)
    {
    // ...
    
    $articleDate = new \DateTime($article->getUpdatedAt());
    $authorDate = new \DateTime($author->getUpdatedAt());
    
    $date = $authorDate > $articleDate ? $authorDate : $articleDate;
    
    $response->setLastModified($date);
    $response->isNotModified($this->getRequest());
    
    return $response;
    }

    Функция Response::isNotModified() сравнивает отправленный с запросом заголовок If-Modified-Since с заголовком Last-Modified. Если есть сходство, статусный код 304 будет установлен для Response.

    Заголовок запроса If-Modified-Since эквивалентен заголовку Last-Modified последнего ответа, отправленного пользователю. Таким образом взаимодействуют между собой пользователь и сервер, а также определяют: обновлён ли кэшированный ресурс или нет.

    Оптимизация кода с помощью валидации

    Основная цель любой стратегии кэширования – облегчить загрузку приложения. Иными словами, чем меньше Вы вызываете ответов 304 в приложении, тем лучше. Именно этим и занимается функция Response::isNotModified(), предлагая простую и эффективную структуру:

    public function showAction($articleSlug)
    {
    // Get the minimum information to compute
    // the ETag or the Last-Modified value
    // (based on the Request, data are retrieved from
    // a database or a key-value store for instance)
    $article = // ...
    
    // create a Response with a ETag and/or a Last-Modified header
    $response = new Response();
    $response->setETag($article->computeETag());
    $response->setLastModified($article->getPublishedAt());
    
    // Check that the Response is not modified for the given Request
    if ($response->isNotModified($this->getRequest())) {
    // return the 304 Response immediately
    return $response;
    } else {
    // do more work here - like retrieving more data
    $comments = // ...
    
    // or render a template with the $response you've already started
    return $this->render(
    
    'MyBundle:MyController:article.html.twig',
    array('article' => $article, 'comments' => $comments),
    $response
    );
    }
    }

    Если Response не изменяется, isNotModified() автоматически вставляет ответный код статуса 304, удаляет содержание, а также некоторые заголовки, которых не должно быть при указанном коде статуса.

    Изменение ответа

    Итак, мы пришли к выводу, что каждый URL является точным отображением целевого ресурса. По умолчанию, HTTP кэширование производится с использованием URL ресурса в качестве ключа кэша. Если два человека запрашивают один и тот же URL ресурса, который будет сохранён в кэше, второй человек получит кэшированную версию.

    Иногда этого недостаточно и разные варианты одного и того же URL необходимо будет кэшировать, основываясь на одном или более значениях заголовка запроса. Например, когда Вы уплотняете страницы, если клиент это поддерживает, любой заданный URL имеет два варианта отображения: один – когда клиент поддерживает уплотнение, другой – не поддерживает. Данное определение производится с помощью значения заголовка запроса Accept-Encoding.

    В таком случае, нам нужно, чтобы кэш сохранил оба варианта ответа для определённого URL и выдать их, основываясь на значении запроса Accept-Encoding. Для этого используется заголовок ответа Vary, представляющий из себя разделённый запятыми список заголовков, значения которых приводят в действие различные варианты отображения запрашиваемого ресурса:

    Vary: Accept-Encoding, User-Agent

    Заголовок Vary будет кэшировать различные варианты каждого ресурса, основываясь на URL значениях заголовков запросов Accept-Encoding и User-Agent.

    Объект Response предлагает понятный интерфейс для управления заголовком Vary:

    // set one vary header
    $response->setVary('Accept-Encoding');
    
    // set multiple vary headers
    $response->setVary(array('Accept-Encoding', 'User-Agent'));

    Функция setVary() содержит название заголовка или же целый для которых происходит изменение ответа.

    “Завершение” и валидация

    Естественно, Вы можете пользоваться двумя моделями в пределах одного Response. Поскольку “завершение” превосходит валидацию, Вы легко можете извлечь выгоду из лучшей, или из обеих моделей. Иными словами, используя “завершение” и валидацию, Вы можете проинструктировать кэш в обработке хранящегося в нём содержания, совершая перепроверку через определённый интервал (“завершение”), чтобы убедиться в валидности содержания.

    Другие функции ответа

    Класс Response предлагает гораздо больше функций, подобных кэшу. Вот наиболее полезные из них:

    // Marks the Response stale
    $response->expire();
    
    // Force the response to return a proper 304 response with no content
    $response->setNotModified();

    К тому же, большинство сходных с кэшем HTTP заголовков можно установить с помощью единственной функции setCache():

    // Set cache settings in one call
    $response->setCache(array(
    'etag' => $etag,
    'last_modified' => $date,
    'max_age' => 10,
    's_maxage' => 10,
    'public' => true,
    // 'private' => true,
    ));

    Использование интеграции с обратным прокси-сервером (ESI)

    Шлюз-кэш – это отличный способ повысить производительность Вашего сайта. Но у него имеется одно ограничение: шлюз-кэш кэширует только страницы целиком. Если не получается кэшировать страницу целиком, или она содержит динамические составляющие – Вам не повезло. К счастью, Symfony2 предлагает решение для таких случаев, основанное на технологии, названной ESI, или Интеграцией с Обратным Прокси-Сервером (Edge Side Includes). Akamaï написал эту спецификацию почти 10 лет назад и она позволяет использовать различную стратегию кэширования для для различных же компонентов страницы.

    Спецификация ESI описывает тэги, которые Вы можете вставить в страницы для взаимодействия со шлюз-кэшем. Только один таг, include, применяется в Symfony2, поскольку он является единственным полезным тэгом вне контекста Akamaï:

    <html>
    <body>
    Some content
    
    <!-- Embed the content of another page here -->
    <esi:include src="http://..." />
    
    More content
    </body>
    </html>

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

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

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

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

    Чтобы использовать ESI, убедитесь, что включили его в настройки приложения:

    YAML:

    # app/config/config.yml
    framework:
    # ...
    esi: { enabled: true }

    XML:

    <!-- app/config/config.xml -->
    <framework:config ...>
    <!-- ... -->
    <framework:esi enabled="true" />
    </framework:config>

    PHP:

    // app/config/config.php
    $container->loadFromExtension('framework', array(
    // ...
    'esi' => array('enabled' => true),
    ));

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

    public function indexAction()
    {
    $response = $this->render('MyBundle:MyController:index.html.twig');
    $response->setSharedMaxAge(600);
    
    return $response;
    }

    В следующем примере мы установили 10-минутный жизненный цикл для полностраничного кэша. А теперь давайте включим новостной код в шаблон путём вложения действия. Это можно сделать с помощью хелпера render.

    Поскольку вложенное содержание исходит из другой страницы, (или контроллера), Symfony2 использует стандартный хелпер render для настройки ESI тэгов:

    Twig:

    {% render '...:news' with {}, {'standalone': true} %}

    PHP:

    <?php echo $view['actions']->render('...:news', array(), array('standalone' => true)) ?>

    Устанавливая standalone на true, Вы сообщаете Symfony2, что действие должно визуализироваться в качестве ESI тэга. Вам может стать интересно: зачем использовать хелпер вместо того, чтобы прописать ESI тэг собственноручно? Дело в том, что использование хелпера позволяет приложению работать даже без установки шлюз-кэша. Давайте рассмотрим как это работает.

    Когда standalone установлен на false (по умолчанию), Symfony2 объединяет всё включённое содержание страницы перед тем, как отправить ответ пользователю. Но, если standalone установлен на true, и если Symfony2 определяет взаимодействие со шлюз-кэшем, поддерживающим ESI, генерируется ESI тэг присоединения. Но если шлюз-кэш отсутствует или он не поддерживает ESI, Symfony2 объединить содержание страницы так же, как это происходило бы при условии, что standalone установлен на false.

    Symfony2 определяет поддержку ESI шлюз-кэшем с помощью другой спецификации Akamaï, которая поддерживается реверсным прокси-сервером Symfony2.

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

    public function newsAction()
    {
    // ...
    
    $response->setSharedMaxAge(60);
    }

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

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

    YAML:

    # app/config/routing.yml
    _internal:
    resource: "@FrameworkBundle/Resources/config/routing/internal.xml"
    prefix: /_internal

    XML:

    <!-- app/config/routing.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    
    <routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
    
    <import resource="@FrameworkBundle/Resources/config/routing/internal.xml" prefix="/_internal" />
    </routes>

    PHP:

    // app/config/routing.php
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    
    $collection->addCollection($loader->import('@FrameworkBundle/Resources/config/routing/internal.xml', '/_internal'));
    
    return $collection;

    Поскольку указанный путь позволяет сделать все действия доступными посредством URL, Вы могли бы пожелать защитить его, используя Symfony2 firewall (путём предоставления доступа к IP диапазону Вашего реверсного прокси-сервера).

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

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

    Хелпер render поддерживает ещё две полезные опции:

  • alt: используется в качестве alt атрибута ESI тэга, позволяющего Вам указать альтернативный URL в тех случаях, когда невозможно найти src;
  • ignore_errors: установленный на “true” атрибут onerror добавится в ESI со значением continue, указывающим, что, в случае ошибки шлюз-кэш попросту удалит ESI тэг.
  • Аннулирование кэша

    “В компьтерной науке всего две трудности – аннулирование кэша и присваивание имён.” – Фил Карлтон (Phil Karlton)

    Вам не понадобится аннулировать кэшированные данные, поскольку аннулирование предусмотрено в моделях HTTP кэша. Если Вы используете валидацию, Вам не придётся что-либо аннулировать по определению. Если же Вы используете “завершение” и Вам необходимо аннулировать ресурс, это означает, что установленная дата истечения срока действия слишком велика.

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

    Фактически, все реверсные прокси-сервера предоставляют способы удалить кэшированные данные, однако, следует избегать их насколько это возможно. Самым стандартным способом является очистка кэша для заданного URL путём запрашивания его с помощью особой HTTP функции PURGE.

    Таким вот образом Вы можете настроить реверсный прокси-сервер Symfony2 для поддержки HTTP функции PURGE:

    // app/AppCache.php
    class AppCache extends Cache
    {
    protected function invalidate(Request $request)
    {
    if ('PURGE' !== $request->getMethod()) {
    return parent::invalidate($request);
    }
    
    $response = new Response();
    if (!$this->store->purge($request->getUri())) {
    $response->setStatusCode(404, 'Not purged');
    } else {
    $response->setStatusCode(200, 'Purged');
    }
    
    return $response;
    }
    }

    Вам необходимо каким-либо защитить способом функцию PURGE от несанкционированного использования другими людьми.

    Резюме

    Symfony2 разработано для соблюдения проверенного свода правил – HTTP. Кэширование не является исключением. Освоить систему кэширования Symfony2 значит: ознакомиться и эффективно использовать модели HTTP кэша. Иными словами, вместо того, чтобы полагаться только на документацию и пример кода Symfony2, у Вас есть доступ к миру знаний, сходным с HTTP кэшированием и шлюз-кэшами, такими как Varnish.



    Похожие записи:

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

    Ваш e-mail не будет опубликован. Обязательные поля помечены *

    *

    Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>