Валидация HTTP-кеша

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

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

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

Tip

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

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

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

Tip

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

Валидация с заголовком ETag

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

Чтобы увидеть простую реализацию, сгенерируйте ETag, как md5 содержимого:

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

use Symfony\Component\HttpFoundation\Request;

class DefaultController extends Controller
{
    public function homepageAction(Request $request)
    {
        $response = $this->render('static/homepage.html.twig');
        $response->setEtag(md5($response->getContent()));
        $response->setPublic(); // make sure the response is public/cacheable
        $response->isNotModified($request);

        return $response;
    }
}

Метод isNotModified() сравнивает заголовок If-None-Match с заголовком ответа ETag. Если они совпадают, метод автоматически устанавливает в Response статус-код 304.

Note

Кеш устанавливает в запросе заголовок If-None-Match к ETag первоначального кешированного ответа перед тем, как отправлять запрос обратно в приложение. Это то, как общаются друг с другом кеш и сервер, и как они решают, был ли обновлён ресурс с момента его кеширования.

Этот алгоритм достаточно простой и очень общий, но вам нужно создать целый ``Response``до того, как вы сможете вычислить ETag, который является субоптимальным. Другими словами, он экономит пропусную способность, но не циклы центрального процессора.

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

Tip

Symfony также поддерживаетслабые ETags, передавая true в качестве второго аргумента метода setEtag().

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

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// src/AppBundle/Controller/ArticleController.php
namespace AppBundle\Controller;

// ...
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use AppBundle\Entity\Article;

class ArticleController extends Controller
{
    public function showAction(Article $article, Request $request)
    {
        $author = $article->getAuthor();

        $articleDate = new \DateTime($article->getUpdatedAt());
        $authorDate = new \DateTime($author->getUpdatedAt());

        $date = $authorDate > $articleDate ? $authorDate : $articleDate;

        $response = new Response();
        $response->setLastModified($date);
        // Установить ответ как публичный. Иначе он будет приватным по умолчанию.
        $response->setPublic();

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

        // ... сделать больше для наполнения ответа полным содержимым

        return $response;
    }
}

Метод isNotModified() сравнивает заголовок If-Modified-Since с заголовком ответа Last-Modified. Если они эквивалентны, то в Response будет установлен статус-код 304.

Note

Кеш устанавливает загловок If-Modified-Since в запросе к Last-Modified исходного кешированного ответа перед тем, как отправлять запрос обратно к приложению. Это то, как общаются друг с другом кеш и сервер и решают, был ли обновлён ресурс с тех пор, как он был кеширован.

Оптимизация вашего кода с валидацией

Главной целью любой стратегии кеширования является облегчение нагрузки на приложение. Другими словами, чем меньше вы делаете в вашем приложении, чтобы вернуть ответ 304, тем лучше. Метод Response::isNotModified() делает именно так, путём раскрытия простой и действенной схемы:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// src/AppBundle/Controller/ArticleController.php
namespace AppBundle\Controller;

// ...
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;

class ArticleController extends Controller
{
    public function showAction($articleSlug, Request $request)
    {
        // Получить минимум информации для вычисления
        // значения ETag или Last-Modified
        // (основываясь на Request, данные извлекаются из
        // базы данных или, например, хранилища ключевых значений)
        $article = ...;

        // создать Response с загловком ETag и/или Last-Modified
        $response = new Response();
        $response->setETag($article->computeETag());
        $response->setLastModified($article->getPublishedAt());

        // Установить ответ, как публичный. Иначе он будет приватным по умолчанию.
        $response->setPublic();

        // Проверить, чтоб Response не был изменён для заданного Request
        if ($response->isNotModified($request)) {
            // немедленно вернуть ответ 304
            return $response;
        }

        // сделать больше работы - например, извлечь больше данных
        $comments = ...;

        // или отобразить шаблон с $response, который вы уже начали
        return $this->render('article/show.html.twig', array(
            'article' => $article,
            'comments' => $comments
        ), $response);
    }
}

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

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