HTTP Cache Validation

When a resource needs to be updated as soon as a change is made to the underlying data, the expiration model falls short. With the expiration model, the application won't be asked to return the updated response until the cache finally becomes stale.

基になるデータに変更が加えられるとすぐにリソースを更新する必要がある場合、有効期限モデルは不十分です。有効期限モデルでは、キャッシュが最終的に古くなるまで、アプリケーションは更新された応答を返すように求められません。

The validation model addresses this issue. Under this model, the cache continues to store responses. The difference is that, for each request, the cache asks the application if the cached response is still valid or if it needs to be regenerated. If the cache is still valid, your application should return a 304 status code and no content. This tells the cache that it's OK to return the cached response.

検証モデルはこの問題に対処します。このモデルでは、キャッシュは引き続き応答を格納します。違いは、リクエストごとに、キャッシュされたレスポンスがまだ有効かどうか、または再生成する必要があるかどうかをキャッシュがアプリケーションに尋ねることです。キャッシュがまだ有効な場合、アプリケーションは 304 ステータス コードを返し、コンテンツは返しません。これにより、キャッシュされた応答を返してもよいことがキャッシュに伝えられます。

Under this model, you only save CPU if you're able to determine that the cached response is still valid by doing less work than generating the whole page again (see below for an implementation example).

このモデルでは、ページ全体を再度生成するよりも少ない作業で、キャッシュされた応答がまだ有効であると判断できる場合にのみ、CPU を節約できます (実装例については以下を参照してください)。

Tip

ヒント

The 304 status code means "Not Modified". It's important because with this status code the response does not contain the actual content being requested. Instead, the response only consists of the response headers that tells the cache that it can use its stored version of the content.

304 ステータス コードは「変更されていません」を意味します。このステータス コードでは、要求されている実際のコンテンツが応答に含まれていないため、これは重要です。代わりに、応答は、キャッシュに格納されたバージョンのコンテンツを使用できることをキャッシュに伝える応答ヘッダーのみで構成されます。

Like with expiration, there are two different HTTP headers that can be used to implement the validation model: ETag and Last-Modified.

有効期限と同様に、検証モデルの実装に使用できる HTTP ヘッダーには、ETag と Last-Modified の 2 つがあります。
有効期限と検証

You can use both validation and expiration within the same Response. As expiration wins over validation, you can benefit from the best of both worlds. In other words, by using both expiration and validation, you can instruct the cache to serve the cached content, while checking back at some interval (the expiration) to verify that the content is still valid.

同じレスポンス内で検証と有効期限の両方を使用できます。有効期限が検証よりも優先されるため、両方の長所を活かすことができます。つまり、有効期限と検証の両方を使用することで、コンテンツがまだ有効であることを確認するために一定の間隔 (有効期限) で戻ってチェックしながら、キャッシュされたコンテンツを提供するようにキャッシュに指示できます。

Tip

ヒント

You can also define HTTP caching headers for expiration and validation by using annotations. See the FrameworkExtraBundle documentation.

アノテーションを使用して、有効期限と検証用の HTTP キャッシュ ヘッダーを定義することもできます。 FrameworkExtraBundle のドキュメントを参照してください。

Validation with the ETag Header

The HTTP ETag ("entity-tag") header is an optional HTTP header whose value is an arbitrary string that uniquely identifies one representation of the target resource. It's entirely generated and set by your application so that you can tell, for example, if the /about resource that's stored by the cache is up-to-date with what your application would return.

HTTP ETag ("entity-tag") ヘッダーはオプションの HTTP ヘッダーであり、その値はターゲット リソースの 1 つの表現を一意に識別する任意の文字列です。アプリケーションによって完全に生成および設定されるため、たとえば、キャッシュによって格納されている /about リソースが、アプリケーションが返すものと最新のものであるかどうかがわかります。

An ETag is like a fingerprint and is used to quickly compare if two different versions of a resource are equivalent. Like fingerprints, each ETag must be unique across all representations of the same resource.

ETag は指紋のようなもので、リソースの 2 つの異なるバージョンが同等かどうかをすばやく比較するために使用されます。フィンガープリントと同様に、各 ETag は同じリソースのすべての表現で一意である必要があります。

To see a short implementation, generate the ETag as the md5 of the content:

簡単な実装を確認するには、コンテンツの md5 として ETag を生成します。
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;
    }
}

The isNotModified() method compares the If-None-Match header with the ETag response header. If the two match, the method automatically sets the Response status code to 304.

isNotModified() メソッドは、If-None-Match ヘッダーと ETag 応答ヘッダーを比較します。2 つが一致すると、メソッドは自動的に応答ステータス コードを 304 に設定します。

Note

ノート

When using mod_deflate or mod_brotli in Apache 2.4, the original ETag value is modified (e.g. if ETag was foo, Apache turns it into foo-gzip or foo-br), which breaks the ETag-based validation.

Apache 2.4 で mod_deflate または mod_brotli を使用すると、元の ETag 値が変更され (たとえば、ETag が foo の場合、Apache はそれを foo-gzip または foo-br に変換します)、ETag ベースの検証が壊れます。

You can control this behavior with the DeflateAlterETag and BrotliAlterETag directives. Alternatively, you can use the following Apache configuration to keep both the original ETag and the modified one when compressing responses:

この動作は、DeflateAlterETag および BrotliAlterETag ディレクティブで制御できます。または、次の Apache 構成を使用して、応答を圧縮するときに元の ETag と変更された ETag の両方を保持できます。
1
RequestHeader edit "If-None-Match" '^"((.*)-(gzip|br))"$' '"$1", "$2"'

Note

ノート

The cache sets the If-None-Match header on the request to the ETag of the original cached response before sending the request back to the app. This is how the cache and server communicate with each other and decide whether or not the resource has been updated since it was cached.

キャッシュは、要求をアプリに送り返す前に、要求の If-None-Match ヘッダーを元のキャッシュされた応答の ETago に設定します。これは、キャッシュとサーバーが相互に通信し、リソースがキャッシュされてから更新されたかどうかを判断する方法です。

This algorithm works and is very generic, but you need to create the whole Response before being able to compute the ETag, which is sub-optimal. In other words, it saves on bandwidth, but not CPU cycles.

このアルゴリズムは機能し、非常に一般的ですが、ETag を計算する前に全体のResponse を作成する必要があり、これは最適ではありません。つまり、帯域幅は節約されますが、CPU サイクルは節約されません。

In the HTTP Cache Validation section, you'll see how validation can be used more intelligently to determine the validity of a cache without doing so much work.

HTTP キャッシュの検証セクションでは、検証をよりインテリジェントに使用して、多くの作業を行うことなくキャッシュの有効性を判断する方法について説明します。

Tip

ヒント

Symfony also supports weak ETag s by passing true as the second argument to the setEtag() method.

symfony は、setEtag() メソッドの 2 番目の引数として true を渡すことで、弱い ETag もサポートします。

Validation with the Last-Modified Header

The Last-Modified header is the second form of validation. According to the HTTP specification, "The Last-Modified header field indicates the date and time at which the origin server believes the representation was last modified." In other words, the application decides whether or not the cached content has been updated based on whether or not it's been updated since the response was cached.

Last-Modified ヘッダーは、検証の 2 番目の形式です。 HTTP 仕様によると、「Last-Modified ヘッダー フィールドは、表現が最後に変更されたとオリジン サーバーが判断した日時を示します。」つまり、アプリケーションは、応答がキャッシュされてからコンテンツが更新されたかどうかに基づいて、キャッシュされたコンテンツが更新されたかどうかを判断します。

For instance, you can use the latest update date for all the objects needed to compute the resource representation as the value for the Last-Modified header value:

たとえば、Last-Modifiedheader 値の値として、リソース表現を計算するために必要なすべてのオブジェクトの最新の更新日を使用できます。
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);
        // Set response as public. Otherwise it will be private by default.
        $response->setPublic();

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

        // ... do more work to populate the response with the full content

        return $response;
    }
}

The isNotModified() method compares the If-Modified-Since header with the Last-Modified response header. If they are equivalent, the Response will be set to a 304 status code.

isNotModified() メソッドは、If-Modified-Since ヘッダーと Last-Modifiedresponse ヘッダーを比較します。それらが等しい場合、レスポンスは a304 ステータス コードに設定されます。

Note

ノート

The cache sets the If-Modified-Since header on the request to the Last-Modified of the original cached response before sending the request back to the app. This is how the cache and server communicate with each other and decide whether or not the resource has been updated since it was cached.

キャッシュは、要求をアプリに送り返す前に、要求の If-Modified-Since ヘッダーを元のキャッシュされた応答の Last-Modified に設定します。これは、キャッシュとサーバーが相互に通信し、リソースがキャッシュされてから更新されたかどうかを判断する方法です。

Optimizing your Code with Validation

The main goal of any caching strategy is to lighten the load on the application. Put another way, the less you do in your application to return a 304 response, the better. The Response::isNotModified() method does exactly that:

キャッシュ戦略の主な目的は、アプリケーションの負荷を軽減することです。別の言い方をすれば、アプリケーションで 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
    {
        // Get the minimum information to compute
        // the ETag or the Last-Modified value
        // (based on the Request, data is retrieved from
        // a database or a key-value store for instance)
        $article = ...;

        // create a Response with an ETag and/or a Last-Modified header
        $response = new Response();
        $response->setEtag($article->computeETag());
        $response->setLastModified($article->getPublishedAt());

        // Set response as public. Otherwise it will be private by default.
        $response->setPublic();

        // Check that the Response is not modified for the given Request
        if ($response->isNotModified($request)) {
            // return the 304 Response immediately
            return $response;
        }

        // do more work here - like retrieving more data
        $comments = ...;

        // or render a template with the $response you've already started
        return $this->render('article/show.html.twig', [
            'article' => $article,
            'comments' => $comments,
        ], $response);
    }
}

When the Response is not modified, the isNotModified() automatically sets the response status code to 304, removes the content, and removes some headers that must not be present for 304 responses (see setNotModified()).

応答が変更されていない場合、isNotModified() は自動的に応答ステータス コードを 304 に設定し、コンテンツを削除し、304 応答に存在してはならない一部のヘッダーを削除します (setNotModified() を参照)。