Компонент HttpKernel: HttpKernelInterface

Компонент HttpKernel: HttpKernelInterface

В заключении второй главы этой киги, я говорил о наибольшем преимуществе использовани компонентов Symfony: the взаимодействие между всеми фреймворками и приложениями, использующими их. Давайте сделаем большой шаг на пути к этой цели, заставив наш фреймворк реализовать HttpKernelInterface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace Symfony\Component\HttpKernel;

// ...
interface HttpKernelInterface
{
    /**
     * @return Response A Response instance
     */
    public function handle(
        Request $request,
        $type = self::MASTER_REQUEST,
        $catch = true
    );
}

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

Обновите ваш фреймворк так, чтобы он реализовывал этот интерфейс:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// example.com/src/Framework.php

// ...
use Symfony\Component\HttpKernel\HttpKernelInterface;

class Framework implements HttpKernelInterface
{
    // ...

    public function handle(
        Request $request,
        $type = HttpKernelInterface::MASTER_REQUEST,
        $catch = true
    ) {
        // ...
    }
}

Даже если это изменение выглядит простым, оно многое привносит! Давайте поговорим о самом впечатляющем: прозрачной поддержке HTTP-кеширования.

Класс HttpCache реализует полностью функциональный обратный прокси, написанный на PHP; он реализует HttpKernelInterface и оборачивает другой экземпляр HttpKernelInterface:

1
2
3
4
5
6
7
8
// example.com/web/front.php
$framework = new Simplex\Framework($dispatcher, $matcher, $resolver);
$framework = new HttpKernel\HttpCache\HttpCache(
    $framework,
    new HttpKernel\HttpCache\Store(__DIR__.'/../cache')
);

$framework->handle($request)->send();

Вот и всё, что нужно, чтобы добавить поддержку HTTP-кеширования в наш фреймворк. Разве это не потрясающе?

Конфигурирование кеша должно быть произведено через заголовки HTTP-кеша. Например, чтобы кешировать ответ на 10 секунд, используйте метод Response::setTtl():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// example.com/src/Calendar/Controller/LeapYearController.php

// ...
public function indexAction(Request $request, $year)
{
    $leapyear = new LeapYear();
    if ($leapyear->isLeapYear($year)) {
        $response = new Response('Yep, this is a leap year!');
    } else {
        $response = new Response('Nope, this is not a leap year.');
    }

    $response->setTtl(10);

    return $response;
}

Tip

Если вы, как и я, запускаете ваш фреймворк из командной строки, симулируя запросы(Request::create('/is_leap_year/2012')), то вы можете с лёгкостью отладить экземпляры Ответа, сбросив их пресдтавление строки (echo $response;), так как оно отображает все заголовки, а также содержимое ответа.

Чтобы удостовериться, что всё работает правильно, добавьте произвольное число к содержимому ответа и проверьте, чтобы он менялся только каждые 10 секунд:

1
$response = new Response('Yep, this is a leap year! '.rand());

Note

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

Использование заголовков HTTP-кеширования для управления кешем вашего приложения - очень мощный инструмент, позволяющий вам точно настроить вашу стратегию кеширования, так как вы можете использовать как модель истечения срока, так и валидации в HTTP спецификации. Если вам не комфортно с этими концептами, прочтите главу HTTP-кеширование в документации Symfony.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$date = date_create_from_format('Y-m-d H:i:s', '2005-10-15 10:00:00');

$response->setCache(array(
    'public'        => true,
    'etag'          => 'abcde',
    'last_modified' => $date,
    'max_age'       => 10,
    's_maxage'      => 10,
));

// эквивалентно следующему коду
$response->setPublic();
$response->setEtag('abcde');
$response->setLastModified($date);
$response->setMaxAge(10);
$response->setSharedMaxAge(10);

При использовании модели валидации, метод isNotModified() позволяет вам с лёгкостью укоротить время ответа, кратко замкнув поколение ответа как можно раньше:

1
2
3
4
5
6
7
8
9
$response->setETag('whatever_you_compute_as_an_etag');

if ($response->isNotModified($request)) {
    return $response;
}

$response->setContent('The computed content of the response');

return $response;

Исползование HTTP-кеширования - это отлично, но что, если вы не можете кешировать целую страницу? Что, если вы можете кешировать всё, кроме боковой панели, которая более динамическая, чем остальное содержимое? Включения крайней стороны (Edge Side Includes)(ESI) придут к вам на помощь! Вместо генерирования всего содержимого за раз, ESI позволяют вам отметить область страницы, как содержимое вызова подзапроса:

1
2
3
4
5
Это содержимое вашей страницы

2012 - это високосный год? 

Другое содержимое

Для того, чтобы ESI-теги поддерживались HttpCache, вам нужно передать ему экземпляр класса ESI. Класс ESI автоматически анализирует ESI-теги и заставлять подзапросы конвертировать их в правильное содержимое:

1
2
3
4
5
$framework = new HttpKernel\HttpCache\HttpCache(
    $framework,
    new HttpKernel\HttpCache\Store(__DIR__.'/../cache'),
    new HttpKernel\HttpCache\Esi()
);

Note

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

При использовании сложных стратегий HTTP-кеширования и/или множества ESI-тегов включений, может быть сложно понять, почему и когда источник должен быть кеширован. Чтобы облегчить отладку, вы можете включить режим отладки:

1
2
3
4
5
6
$framework = new HttpKernel\HttpCache\HttpCache(
    $framework,
    new HttpKernel\HttpCache\Store(__DIR__.'/../cache'),
    new HttpKernel\HttpCache\Esi(),
    array('debug' => true)
);

Режим отладки добавляет заголовок X-Symfony-Cache к каждому запросу, который описывает, что сделал слой кеша:

1
2
3
X-Symfony-Cache:  GET /is_leap_year/2012: stale, invalid, store

X-Symfony-Cache:  GET /is_leap_year/2012: fresh

HttpCache имеет множество функций, вроде поддержки расширений HTTP Cache-Control stale-while-revalidate и stale-if-error, как определено в RFC 5861.

С добавлением единственного интерфейса, наш фреймворк теперь может пользоваться преимуществами многих встроенных в компонент HttpKernel функций; HTTP-кеширование - это лишь одна из них, но она важная, так как она может заставить ваши приложения летать!