Skip to content

Commit 6d58fec

Browse files
committed
Re-working the HTTP cache chapter
Removing a lot of theory, removing public/private conversation
1 parent 66d2cf0 commit 6d58fec

File tree

4 files changed

+308
-470
lines changed

4 files changed

+308
-470
lines changed

cache/cache_invalidation.rst

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
.. index::
2+
single: Cache; Invalidation
3+
4+
.. _http-cache-invalidation:
5+
6+
Cache Invalidation
7+
~~~~~~~~~~~~~~~~~~
8+
9+
"There are only two hard things in Computer Science: cache invalidation
10+
and naming things." -- Phil Karlton
11+
12+
Once an URL is cached by a gateway cache, the cache will not ask the
13+
application for that content anymore. This allows the cache to provide fast
14+
responses and reduces the load on your application. However, you risk
15+
delivering outdated content. A way out of this dilemma is to use long
16+
cache lifetimes, but to actively notify the gateway cache when content
17+
changes. Reverse proxies usually provide a channel to receive such
18+
notifications, typically through special HTTP requests.
19+
20+
.. caution::
21+
22+
While cache invalidation is powerful, avoid it when possible. If you fail
23+
to invalidate something, outdated caches will be served for a potentially
24+
long time. Instead, use short cache lifetimes or use the validation model,
25+
and adjust your controllers to perform efficient validation checks as
26+
explained in :ref:`optimizing-cache-validation`.
27+
28+
Furthermore, since invalidation is a topic specific to each type of reverse
29+
proxy, using this concept will tie you to a specific reverse proxy or need
30+
additional efforts to support different proxies.
31+
32+
Sometimes, however, you need that extra performance you can get when
33+
explicitly invalidating. For invalidation, your application needs to detect
34+
when content changes and tell the cache to remove the URLs which contain
35+
that data from its cache.
36+
37+
.. tip::
38+
39+
If you want to use cache invalidation, have a look at the
40+
`FOSHttpCacheBundle`_. This bundle provides services to help with various
41+
cache invalidation concepts and also documents the configuration for a
42+
couple of common caching proxies.
43+
44+
If one content corresponds to one URL, the ``PURGE`` model works well.
45+
You send a request to the cache proxy with the HTTP method ``PURGE`` (using
46+
the word "PURGE" is a convention, technically this can be any string) instead
47+
of ``GET`` and make the cache proxy detect this and remove the data from the
48+
cache instead of going to the application to get a response.
49+
50+
Here is how you can configure the Symfony reverse proxy to support the
51+
``PURGE`` HTTP method::
52+
53+
// app/AppCache.php
54+
55+
use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache;
56+
use Symfony\Component\HttpFoundation\Request;
57+
use Symfony\Component\HttpFoundation\Response;
58+
// ...
59+
60+
class AppCache extends HttpCache
61+
{
62+
protected function invalidate(Request $request, $catch = false)
63+
{
64+
if ('PURGE' !== $request->getMethod()) {
65+
return parent::invalidate($request, $catch);
66+
}
67+
68+
if ('127.0.0.1' !== $request->getClientIp()) {
69+
return new Response(
70+
'Invalid HTTP method',
71+
Response::HTTP_BAD_REQUEST
72+
);
73+
}
74+
75+
$response = new Response();
76+
if ($this->getStore()->purge($request->getUri())) {
77+
$response->setStatusCode(200, 'Purged');
78+
} else {
79+
$response->setStatusCode(404, 'Not found');
80+
}
81+
82+
return $response;
83+
}
84+
}
85+
86+
.. caution::
87+
88+
You must protect the ``PURGE`` HTTP method somehow to avoid random people
89+
purging your cached data.
90+
91+
**Purge** instructs the cache to drop a resource in *all its variants*
92+
(according to the ``Vary`` header, see above). An alternative to purging is
93+
**refreshing** a content. Refreshing means that the caching proxy is
94+
instructed to discard its local cache and fetch the content again. This way,
95+
the new content is already available in the cache. The drawback of refreshing
96+
is that variants are not invalidated.
97+
98+
In many applications, the same content bit is used on various pages with
99+
different URLs. More flexible concepts exist for those cases:
100+
101+
* **Banning** invalidates responses matching regular expressions on the
102+
URL or other criteria;
103+
* **Cache tagging** lets you add a tag for each content used in a response
104+
so that you can invalidate all URLs containing a certain content.

cache/cache_vary.rst

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
.. index::
2+
single: Cache; Vary
3+
single: HTTP headers; Vary
4+
5+
Varying the Response for HTTP Cache
6+
===================================
7+
8+
So far, it's been assumed that each URI has exactly one representation of the
9+
target resource. By default, HTTP caching is done by using the URI of the
10+
resource as the cache key. If two people request the same URI of a cacheable
11+
resource, the second person will receive the cached version.
12+
13+
Sometimes this isn't enough and different versions of the same URI need to
14+
be cached based on one or more request header values. For instance, if you
15+
compress pages when the client supports it, any given URI has two representations:
16+
one when the client supports compression, and one when it does not. This
17+
determination is done by the value of the ``Accept-Encoding`` request header.
18+
19+
In this case, you need the cache to store both a compressed and uncompressed
20+
version of the response for the particular URI and return them based on the
21+
request's ``Accept-Encoding`` value. This is done by using the ``Vary`` response
22+
header, which is a comma-separated list of different headers whose values
23+
trigger a different representation of the requested resource:
24+
25+
.. code-block:: text
26+
27+
Vary: Accept-Encoding, User-Agent
28+
29+
.. tip::
30+
31+
This particular ``Vary`` header would cache different versions of each
32+
resource based on the URI and the value of the ``Accept-Encoding`` and
33+
``User-Agent`` request header.
34+
35+
The ``Response`` object offers a clean interface for managing the ``Vary``
36+
header::
37+
38+
// set one vary header
39+
$response->setVary('Accept-Encoding');
40+
41+
// set multiple vary headers
42+
$response->setVary(array('Accept-Encoding', 'User-Agent'));
43+
44+
The ``setVary()`` method takes a header name or an array of header names for
45+
which the response varies.

cache/expiration.rst

+25-28
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,38 @@ HTTP headers: ``Expires`` or ``Cache-Control``.
2525
You can also define HTTP caching headers for expiration and validation by using
2626
annotations. See the `FrameworkExtraBundle documentation`_.
2727

28+
.. index::
29+
single: Cache; Cache-Control header
30+
single: HTTP headers; Cache-Control
31+
32+
Expiration with the ``Cache-Control`` Header
33+
--------------------------------------------
34+
35+
Most of the time, you will use the ``Cache-Control`` header. Recall that the
36+
``Cache-Control`` header is used to specify many different cache directives::
37+
38+
// Sets the number of seconds after which the response
39+
// should no longer be considered fresh
40+
$response->setSharedMaxAge(600);
41+
42+
The ``Cache-Control`` header would take on the following format (it may have
43+
additional directives):
44+
45+
.. code-block:: text
46+
47+
Cache-Control: public, s-maxage=600
48+
2849
.. index::
2950
single: Cache; Expires header
3051
single: HTTP headers; Expires
3152

3253
Expiration with the ``Expires`` Header
3354
--------------------------------------
3455

56+
An alternative to the ``Cache-Control`` header is ``Expires``. There's no advantage
57+
or disadvantage to either: they're just different ways to set expiration caching
58+
on your response.
59+
3560
According to the HTTP specification, "the ``Expires`` header field gives
3661
the date/time after which the response is considered stale." The ``Expires``
3762
header can be set with the ``setExpires()`` ``Response`` method. It takes a
@@ -60,33 +85,5 @@ the lifetime calculation vulnerable to clock skew. Another limitation
6085
of the ``Expires`` header is that the specification states that "HTTP/1.1
6186
servers should not send ``Expires`` dates more than one year in the future."
6287

63-
.. index::
64-
single: Cache; Cache-Control header
65-
single: HTTP headers; Cache-Control
66-
67-
Expiration with the ``Cache-Control`` Header
68-
--------------------------------------------
69-
70-
Because of the ``Expires`` header limitations, most of the time, you should
71-
use the ``Cache-Control`` header instead. Recall that the ``Cache-Control``
72-
header is used to specify many different cache directives. For expiration,
73-
there are two directives, ``max-age`` and ``s-maxage``. The first one is
74-
used by all caches, whereas the second one is only taken into account by
75-
shared caches::
76-
77-
// Sets the number of seconds after which the response
78-
// should no longer be considered fresh
79-
$response->setMaxAge(600);
80-
81-
// Same as above but only for shared caches
82-
$response->setSharedMaxAge(600);
83-
84-
The ``Cache-Control`` header would take on the following format (it may have
85-
additional directives):
86-
87-
.. code-block:: text
88-
89-
Cache-Control: max-age=600, s-maxage=600
90-
9188
.. _`expiration model`: http://tools.ietf.org/html/rfc2616#section-13.2
9289
.. _`FrameworkExtraBundle documentation`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/cache.html

0 commit comments

Comments
 (0)