Response cache
Caching of whole HTTP responses. Off by default; two settings turn it on.
| Setting | Default | |
|---|---|---|
pageblocks_http_cache | off | Switches it on |
pageblocks_http_cache_ttl | 3600 | Lifetime in seconds |
This is not the same as the Cache service, which stores values you put there yourself. This one stores rendered pages, and does it automatically.
What gets cached
Only a response that is all three of these:
- a
GETrequest, - from an anonymous visitor — not authenticated in the current context, nor in
mgr, - answered with 200.
Anything else goes straight through: forms, the manager, a logged-in user's page, a 404.
Why anonymous only
A cached page is shared by everyone who asks for that URL. One authenticated response in that store and the next visitor gets somebody else's greeting. The rule is narrow on purpose — there is no configuration to widen it, because the failure mode is serving one user's data to another.
The key
The key is the context, plus a hash of the request URI, plus a fingerprint of the visitor's guest state. The query string is part of the URI, so /catalog?page=2 and /catalog?page=3 are separate entries.
The fingerprint is GuestCookies::fingerprint() — an md5 of the registered guest cookies the request arrived with. Three consequences:
- two guests get two entries, so one guest's page is never served to another;
- the same guest gets one entry, so the cache finally works for shoppers;
- a visitor with no guest cookies keeps the key they had, byte for byte. Most visitors are in this group: they share one entry rather than each getting their own, and a cache already on disk stays usable after an upgrade.
A guest cookie must be registered to count
Only cookie names passed to GuestCookies::register() go into the fingerprint. An unregistered cookie holding guest state means the cache cannot tell one guest from another — which is exactly the leak this fixes. See Guests.
The fingerprint is computed once per request and remembered. The key is asked for twice — on read in routing and on write on send — and a token issued in between would otherwise store the answer under a key nobody looked in.
Invalidation happens by itself
Saving a model flushes the whole response cache. You do not tag pages, you do not work out which URLs a change touched, and you do not add a hook when you add a table.
The exception is a model that says saving it changes nothing a visitor can see: set protected bool $flushesResponseCache = false; on it and the flush is skipped. That is for bookkeeping tables — the mail journal opts out, because a digest run of three hundred messages would otherwise flush the store six hundred times in a row and turn the cache into a measurable slowdown. Everything the constructor stores keeps the default, true.
That is deliberately blunt. Precise invalidation is where response caches go wrong: the page that did not update is much more expensive to debug than a cache that refills. On a site where content changes a few times an hour it costs nothing; on one where something writes to the database on every request, the cache will never hold anything — and that is a signal the feature is not for that site rather than a reason to make the rule cleverer.
Restoring a backup flushes it too.
A flush that fails is swallowed on purpose: a cache problem must never break a model save or a request.
Where it sits
The lookup happens in routing, before the request is dispatched — a hit costs one cache read and never reaches a controller. The store happens on send, only for a 200.
Entries live in the MODX cache under pageblocks/http, so clearing the site cache clears them.
When to turn it on
Worth it for a site that is mostly anonymous reading — a catalog, a blog, a landing page under load. A shop counts too: a guest with a cart is cached under their own key, so the catalog is served from the store and the cart stays personal.
Pointless for a personal cabinet, or anything where the visitor is logged in: those requests are never cacheable, and the check costs a little on every one of them.
Check that it is doing anything
Switching it on changes nothing visible, so it is easy to assume it helps. Measure instead: load a page as a guest twice and compare the timings. If the second load is no faster, nothing is being cached — most likely something writes to the database on every request and flushes the store immediately.