Валідація HTTP-кешу
Дата оновлення перекладу 2023-09-19
Валідація HTTP-кешу
Коли ресурсу потрібно оновитися щойно в основних даних були зроблені зміни, модель закінчення строку дії не спрацьовує. У моделі закінчення строку дії, додаток не отримає прохання повернути оновлену відповідь до тих пір, поки кеш не стане застарілим.
З цією проблемою впорається модель валідації. У цій моделі, кеш продовжує зберігати відповіді. Різниця в тому, що для кожного запиту, кеш запитує додаток, чи все ще валідна кешована відповідь, чи її потрібно згенерувати наново. Якщо кеш все ще валідний, ваш додаток повинен повернути статус-код 304 без змісту. Це повідомить кешу, що можна повертати кешовану відповідь.
У цій моделі, ви бережете центральний процесор лише якщо ви можете визначити, що кешована відповідь все ще валідна, зробивши менше роботи, ніж при генеруванні всієї сторінки наново (приклад реалізації дивіться нижче).
Tip
Статус-код 304 означає "Не змінено". Це важливо, так як з цим статус-кодом відповідь не несе запитуваний зміст. Замість цього, відповідь є просто легким набором вказівок, які повідомляють кешу, що він повинен використати збережену версію.
Як і з закінченням строку дії, існує два різних HTTP-заголовки, які можна використати
для реалізації моделі валідації: ETag
і Last-Modified
.
Валідація з заголовком ETag
Заголовок HTTP 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 18 19
// src/Controller/DefaultController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class DefaultController extends AbstractController
{
public function homepage(Request $request): Response
{
$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
При використання mod_deflate
або mod_brotli
в Apache 2.4, оригінальне
значення ETag
модифікується (наприклад, якщо ETag
був foo
, Apache
перетворює його на foo-gzip
або foo-br
), що порушує валідацію, засновану
на ETag
.
Ви можете контролювати цю поведінку з директивами DeflateAlterETag та
BrotliAlterETag. Як варіант, ви можете використати наступну конфігурацію
Apache, щоб залишити як оригінальний ETag
, так і модифікований, при стисканні
відповідей:
1
RequestHeader edit "If-None-Match" '^"((.*)-(gzip|br))"$' '"$1", "$2"'
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/Controller/ArticleController.php
namespace App\Controller;
// ...
use App\Entity\Article;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class ArticleController extends AbstractController
{
public function show(Article $article, Request $request): Response
{
$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/Controller/ArticleController.php
namespace App\Controller;
// ...
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class ArticleController extends AbstractController
{
public function show(string $articleSlug, Request $request): Response
{
// Отримати мінімум інформації для обчислення
// значення 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()).