From 84046cd177cbee671859094c9e930618c0644402 Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Thu, 28 Oct 2021 10:51:22 +0200 Subject: [PATCH 1/5] TASK: Extract the generation of public CacheControl Headers into separate middleware This allows the FullPageCache Package to be used in cases where cacheability and lifetime cannot be extracted from the fusionCache. A possible useCase for that are custom controllers may or may not use fusion for rendering. The public lifetime is now calculated from the header `X-FullPageCache-Lifetime` limited by the Setting `maxPublicCacheTime`. If the response already contains a `CacheControl` header this is skipped. --- Classes/Middleware/CacheHeaderMiddleware.php | 23 ------- .../PublicCacheHeaderMiddleware.php | 60 +++++++++++++++++++ Configuration/Settings.yaml | 3 + README.md | 19 ++++++ 4 files changed, 82 insertions(+), 23 deletions(-) create mode 100644 Classes/Middleware/PublicCacheHeaderMiddleware.php diff --git a/Classes/Middleware/CacheHeaderMiddleware.php b/Classes/Middleware/CacheHeaderMiddleware.php index b0176e8..3fb96ef 100644 --- a/Classes/Middleware/CacheHeaderMiddleware.php +++ b/Classes/Middleware/CacheHeaderMiddleware.php @@ -32,12 +32,6 @@ class CacheHeaderMiddleware implements MiddlewareInterface */ protected $enabled; - /** - * @var boolean - * @Flow\InjectConfiguration(path="maxPublicCacheTime") - */ - protected $maxPublicCacheTime; - public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface { if (!$this->enabled || !$request->hasHeader(RequestCacheMiddleware::HEADER_ENABLED)) { @@ -52,23 +46,6 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface return $response; } - $publicLifetime = 0; - if ($this->maxPublicCacheTime > 0) { - if ($lifetime > 0 && $lifetime < $this->maxPublicCacheTime) { - $publicLifetime = $lifetime; - } else { - $publicLifetime = $this->maxPublicCacheTime; - } - } - - if ($publicLifetime > 0) { - $entryContentHash = md5($response->getBody()->getContents()); - $response->getBody()->rewind(); - $response = $response - ->withHeader('ETag', '"' . $entryContentHash . '"') - ->withHeader('CacheControl', 'public, max-age=' . $publicLifetime); - } - $response = $response ->withHeader(RequestCacheMiddleware::HEADER_ENABLED, ""); diff --git a/Classes/Middleware/PublicCacheHeaderMiddleware.php b/Classes/Middleware/PublicCacheHeaderMiddleware.php new file mode 100644 index 0000000..71fa4c8 --- /dev/null +++ b/Classes/Middleware/PublicCacheHeaderMiddleware.php @@ -0,0 +1,60 @@ +enabled || !$request->hasHeader(RequestCacheMiddleware::HEADER_ENABLED) || $this->maxPublicCacheTime == 0) { + return $next->handle($request); + } + + $response = $next->handle($request); + + if ($response->hasHeader("CacheControl")) { + return $response; + } + + $lifetime = (int)$response->getHeaderLine(RequestCacheMiddleware::HEADER_LIFTIME); + + $publicLifetime = 0; + if ($this->maxPublicCacheTime > 0) { + if ($lifetime > 0 && $lifetime < $this->maxPublicCacheTime) { + $publicLifetime = $lifetime; + } else { + $publicLifetime = $this->maxPublicCacheTime; + } + } + + if ($publicLifetime > 0) { + $entryContentHash = md5($response->getBody()->getContents()); + $response->getBody()->rewind(); + $response = $response + ->withHeader('ETag', '"' . $entryContentHash . '"') + ->withHeader('CacheControl', 'public, max-age=' . $publicLifetime); + } + + return $response; + } +} diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml index 81c1b0c..813b47a 100644 --- a/Configuration/Settings.yaml +++ b/Configuration/Settings.yaml @@ -38,6 +38,9 @@ Neos: 'fullPageRequestCache': middleware: 'Flowpack\FullPageCache\Middleware\RequestCacheMiddleware' position: 'after trustedProxies' + 'fullPagePublicCacheHeader': + middleware: 'Flowpack\FullPageCache\Middleware\PublicCacheHeaderMiddleware' + position: 'before fullPageCacheHeader' 'fullPageCacheHeader': middleware: 'Flowpack\FullPageCache\Middleware\CacheHeaderMiddleware' position: 'after fullPageRequestCache' diff --git a/README.md b/README.md index 4bfe01d..28843f4 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,25 @@ Flowpack: You can also move the cache backend to something faster if available, to improve performance even more. +How it works +------------ + +The package defines three http middlewares: + +- RequestCacheMiddleware: If a request is cacheble the cache is asked first and only if no response is found the + reqeust is passed down the middleware chain. The cache lifetime and tags are determined from the + `X-FullPageCache-Enabled`, `X-FullPageCache-Lifetime` and `X-FullPageCache-Tags` that are set by upstream middlewares + or controllers. + +- PublicCacheHeaderMiddleware: Set `ETag` and `CacheControl` Headers based on the `X-FullPageCache-Enabled` and the + `X-FullPageCache-Lifetime` headers taking the `maxPublicCacheTime` into account. + +- CacheHeaderMiddleware: Connects to the fusion cache and extracts tags plus the allowed lifetime which is then + stored in the response headers `X-FullPageCache-Enabled`, `X-FullPageCache-Lifetime` and `X-FullPageCache-Tags` + +Controllers that want to control the caching behavior directly can set the headers `X-FullPageCache-Enabled`, +`X-FullPageCache-Lifetime` and `X-FullPageCache-Tags` directly. + Warning ------- From 23dfe7e4584498a1fb0910fd03a4bdbaabc448af Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Thu, 28 Oct 2021 10:55:12 +0200 Subject: [PATCH 2/5] TASK: Rename cache control header according to standards Resolves: https://github.com/Flowpack/Flowpack.FullPageCache/issues/14 --- Classes/Middleware/PublicCacheHeaderMiddleware.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Classes/Middleware/PublicCacheHeaderMiddleware.php b/Classes/Middleware/PublicCacheHeaderMiddleware.php index 71fa4c8..55ffc2f 100644 --- a/Classes/Middleware/PublicCacheHeaderMiddleware.php +++ b/Classes/Middleware/PublicCacheHeaderMiddleware.php @@ -32,7 +32,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $response = $next->handle($request); - if ($response->hasHeader("CacheControl")) { + if ($response->hasHeader("Cache-Control")) { return $response; } @@ -52,7 +52,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $response->getBody()->rewind(); $response = $response ->withHeader('ETag', '"' . $entryContentHash . '"') - ->withHeader('CacheControl', 'public, max-age=' . $publicLifetime); + ->withHeader('Cache-Control', 'public, max-age=' . $publicLifetime); } return $response; From 3bd11803990102eccd526c397f081fbdedc2d5b0 Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Thu, 28 Oct 2021 11:54:25 +0200 Subject: [PATCH 3/5] TASK: Make the fusion autoconfiguration opt in via header `X-FullPageCache-EnableFusionAutoconfiguration` The header is set for Neos.Neos:Page automatically once the enabled setting for Flowpack.FullPageCache is set. This prevents the package from interfering with custom controllers and also enables custom controllers to control the caching behavior via the headers `X-FullPageCache-Enabled`, `X-FullPageCache-Lifetime` and `X-FullPageCache-Tags` or when fusion rendering is used by activating `X-FullPageCache-EnableFusionAutoconfiguration` Resolves: https://github.com/Flowpack/Flowpack.FullPageCache/issues/15 --- ...php => FusionAutoconfigurationMiddleware.php} | 11 +++++++++-- .../Middleware/PublicCacheHeaderMiddleware.php | 2 +- Configuration/Objects.yaml | 2 +- Configuration/Settings.yaml | 14 +++++++++----- README.md | 16 +++++++++------- Resources/Private/Fusion/Root.fusion | 8 ++++++++ 6 files changed, 37 insertions(+), 16 deletions(-) rename Classes/Middleware/{CacheHeaderMiddleware.php => FusionAutoconfigurationMiddleware.php} (86%) create mode 100644 Resources/Private/Fusion/Root.fusion diff --git a/Classes/Middleware/CacheHeaderMiddleware.php b/Classes/Middleware/FusionAutoconfigurationMiddleware.php similarity index 86% rename from Classes/Middleware/CacheHeaderMiddleware.php rename to Classes/Middleware/FusionAutoconfigurationMiddleware.php index 3fb96ef..f06d087 100644 --- a/Classes/Middleware/CacheHeaderMiddleware.php +++ b/Classes/Middleware/FusionAutoconfigurationMiddleware.php @@ -11,8 +11,9 @@ use Flowpack\FullPageCache\Aspects\ContentCacheAspect; use Flowpack\FullPageCache\Cache\MetadataAwareStringFrontend; -class CacheHeaderMiddleware implements MiddlewareInterface +class FusionAutoconfigurationMiddleware implements MiddlewareInterface { + public const HEADER_ENABLED = 'X-FullPageCache-EnableFusionAutoconfiguration'; /** * @Flow\Inject @@ -35,11 +36,17 @@ class CacheHeaderMiddleware implements MiddlewareInterface public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface { if (!$this->enabled || !$request->hasHeader(RequestCacheMiddleware::HEADER_ENABLED)) { - return $next->handle($request); + return $next->handle($request)->withoutHeader(self::HEADER_ENABLED); } $response = $next->handle($request); + if (!$response->hasHeader(self::HEADER_ENABLED)) { + return $response; + } else { + $response = $response->withoutHeader(self::HEADER_ENABLED); + } + list($hasUncachedSegments, $tags, $lifetime) = $this->getFusionCacheInformations(); if ($response->hasHeader('Set-Cookie') || $hasUncachedSegments) { diff --git a/Classes/Middleware/PublicCacheHeaderMiddleware.php b/Classes/Middleware/PublicCacheHeaderMiddleware.php index 55ffc2f..4940200 100644 --- a/Classes/Middleware/PublicCacheHeaderMiddleware.php +++ b/Classes/Middleware/PublicCacheHeaderMiddleware.php @@ -32,7 +32,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $response = $next->handle($request); - if ($response->hasHeader("Cache-Control")) { + if ($response->hasHeader("Cache-Control") || !$response->hasHeader(RequestCacheMiddleware::HEADER_ENABLED)) { return $response; } diff --git a/Configuration/Objects.yaml b/Configuration/Objects.yaml index 1402def..c4e0877 100644 --- a/Configuration/Objects.yaml +++ b/Configuration/Objects.yaml @@ -1,4 +1,4 @@ -Flowpack\FullPageCache\Middleware\CacheHeaderMiddleware: +Flowpack\FullPageCache\Middleware\FusionAutoconfigurationMiddleware: properties: contentCache: object: diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml index 813b47a..9d44c59 100644 --- a/Configuration/Settings.yaml +++ b/Configuration/Settings.yaml @@ -38,9 +38,13 @@ Neos: 'fullPageRequestCache': middleware: 'Flowpack\FullPageCache\Middleware\RequestCacheMiddleware' position: 'after trustedProxies' - 'fullPagePublicCacheHeader': + 'fullPageCachePublicHeader': middleware: 'Flowpack\FullPageCache\Middleware\PublicCacheHeaderMiddleware' - position: 'before fullPageCacheHeader' - 'fullPageCacheHeader': - middleware: 'Flowpack\FullPageCache\Middleware\CacheHeaderMiddleware' - position: 'after fullPageRequestCache' + position: 'after fullPageRequestCache 20' + 'fullPageCacheFusionAutoconfiguration': + middleware: 'Flowpack\FullPageCache\Middleware\FusionAutoconfigurationMiddleware' + position: 'after fullPageRequestCache 10' + Neos: + fusion: + autoInclude: + Flowpack.FullPageCache: true diff --git a/README.md b/README.md index 28843f4..7792279 100644 --- a/README.md +++ b/README.md @@ -56,19 +56,21 @@ How it works The package defines three http middlewares: -- RequestCacheMiddleware: If a request is cacheble the cache is asked first and only if no response is found the - reqeust is passed down the middleware chain. The cache lifetime and tags are determined from the +- `fullPageRequestCache`: If a request is cacheble the cache is asked first and only if no response is found the + request is passed down the middleware chain. The cache lifetime and tags are determined from the `X-FullPageCache-Enabled`, `X-FullPageCache-Lifetime` and `X-FullPageCache-Tags` that are set by upstream middlewares or controllers. -- PublicCacheHeaderMiddleware: Set `ETag` and `CacheControl` Headers based on the `X-FullPageCache-Enabled` and the +- `fullPageCachePublicHeader` : Set `ETag` and `CacheControl` Headers based on the `X-FullPageCache-Enabled` and the `X-FullPageCache-Lifetime` headers taking the `maxPublicCacheTime` into account. -- CacheHeaderMiddleware: Connects to the fusion cache and extracts tags plus the allowed lifetime which is then - stored in the response headers `X-FullPageCache-Enabled`, `X-FullPageCache-Lifetime` and `X-FullPageCache-Tags` +- `fullPageCacheFusionAutoconfiguration`: Connects to the fusion cache and extracts tags plus the allowed lifetime which is then + stored in the response headers `X-FullPageCache-Enabled`, `X-FullPageCache-Lifetime` and `X-FullPageCache-Tags`. + This component expects a header `X-FullPageCache-EnableFusionAutoconfiguration` which is set automatically for `Neos.Neos:Page`. -Controllers that want to control the caching behavior directly can set the headers `X-FullPageCache-Enabled`, -`X-FullPageCache-Lifetime` and `X-FullPageCache-Tags` directly. +Custom controllers that want to control the caching behavior directly can set the headers `X-FullPageCache-Enabled`, +`X-FullPageCache-Lifetime` and `X-FullPageCache-Tags` directly while fusion based controllers can enable the autoconfiguration +by setting the header `X-FullPageCache-EnableFusionAutoconfiguration`. Warning ------- diff --git a/Resources/Private/Fusion/Root.fusion b/Resources/Private/Fusion/Root.fusion new file mode 100644 index 0000000..dcac7d4 --- /dev/null +++ b/Resources/Private/Fusion/Root.fusion @@ -0,0 +1,8 @@ +prototype(Neos.Neos:Page) { + httpResponseHead { + headers { + 'X-FullPageCache-EnableFusionAutoconfiguration' = '' + 'X-FullPageCache-EnableFusionAutoconfiguration'.@if.isEnabled = ${Configuration.setting('Flowpack.FullPageCache.enabled') == true} + } + } +} From 725d124dcec802ee2ff6f3012758588bd7e65cf1 Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Fri, 29 Oct 2021 08:56:47 +0200 Subject: [PATCH 4/5] TASK: Move public caching into requestCache Middleware --- .../PublicCacheHeaderMiddleware.php | 60 ------------------- Classes/Middleware/RequestCacheMiddleware.php | 23 +++++++ Configuration/Settings.yaml | 5 +- README.md | 15 +++-- 4 files changed, 31 insertions(+), 72 deletions(-) delete mode 100644 Classes/Middleware/PublicCacheHeaderMiddleware.php diff --git a/Classes/Middleware/PublicCacheHeaderMiddleware.php b/Classes/Middleware/PublicCacheHeaderMiddleware.php deleted file mode 100644 index 4940200..0000000 --- a/Classes/Middleware/PublicCacheHeaderMiddleware.php +++ /dev/null @@ -1,60 +0,0 @@ -enabled || !$request->hasHeader(RequestCacheMiddleware::HEADER_ENABLED) || $this->maxPublicCacheTime == 0) { - return $next->handle($request); - } - - $response = $next->handle($request); - - if ($response->hasHeader("Cache-Control") || !$response->hasHeader(RequestCacheMiddleware::HEADER_ENABLED)) { - return $response; - } - - $lifetime = (int)$response->getHeaderLine(RequestCacheMiddleware::HEADER_LIFTIME); - - $publicLifetime = 0; - if ($this->maxPublicCacheTime > 0) { - if ($lifetime > 0 && $lifetime < $this->maxPublicCacheTime) { - $publicLifetime = $lifetime; - } else { - $publicLifetime = $this->maxPublicCacheTime; - } - } - - if ($publicLifetime > 0) { - $entryContentHash = md5($response->getBody()->getContents()); - $response->getBody()->rewind(); - $response = $response - ->withHeader('ETag', '"' . $entryContentHash . '"') - ->withHeader('Cache-Control', 'public, max-age=' . $publicLifetime); - } - - return $response; - } -} diff --git a/Classes/Middleware/RequestCacheMiddleware.php b/Classes/Middleware/RequestCacheMiddleware.php index e7358d9..e4e209d 100644 --- a/Classes/Middleware/RequestCacheMiddleware.php +++ b/Classes/Middleware/RequestCacheMiddleware.php @@ -52,6 +52,12 @@ class RequestCacheMiddleware implements MiddlewareInterface */ protected $ignoredCookieParams; + /** + * @var boolean + * @Flow\InjectConfiguration(path="maxPublicCacheTime") + */ + protected $maxPublicCacheTime; + public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface { if (!$this->enabled) { @@ -82,6 +88,23 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface ->withoutHeader(self::HEADER_LIFTIME) ->withoutHeader(self::HEADER_TAGS); + $publicLifetime = 0; + if ($this->maxPublicCacheTime > 0) { + if ($lifetime > 0 && $lifetime < $this->maxPublicCacheTime) { + $publicLifetime = $lifetime; + } else { + $publicLifetime = $this->maxPublicCacheTime; + } + } + + if ($publicLifetime > 0) { + $entryContentHash = md5($response->getBody()->getContents()); + $response->getBody()->rewind(); + $response = $response + ->withHeader('ETag', '"' . $entryContentHash . '"') + ->withHeader('Cache-Control', 'public, max-age=' . $publicLifetime); + } + $this->cacheFrontend->set($entryIdentifier,[ 'timestamp' => time(), 'response' => str($response) ], $tags, $lifetime); $response->getBody()->rewind(); return $response->withHeader(self::HEADER_INFO, 'MISS: ' . $entryIdentifier); diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml index 9d44c59..641dcfe 100644 --- a/Configuration/Settings.yaml +++ b/Configuration/Settings.yaml @@ -38,12 +38,9 @@ Neos: 'fullPageRequestCache': middleware: 'Flowpack\FullPageCache\Middleware\RequestCacheMiddleware' position: 'after trustedProxies' - 'fullPageCachePublicHeader': - middleware: 'Flowpack\FullPageCache\Middleware\PublicCacheHeaderMiddleware' - position: 'after fullPageRequestCache 20' 'fullPageCacheFusionAutoconfiguration': middleware: 'Flowpack\FullPageCache\Middleware\FusionAutoconfigurationMiddleware' - position: 'after fullPageRequestCache 10' + position: 'after fullPageRequestCache' Neos: fusion: autoInclude: diff --git a/README.md b/README.md index 7792279..df6ded7 100644 --- a/README.md +++ b/README.md @@ -54,19 +54,18 @@ You can also move the cache backend to something faster if available, to improve How it works ------------ -The package defines three http middlewares: +The package defines two http middlewares: -- `fullPageRequestCache`: If a request is cacheble the cache is asked first and only if no response is found the +- `RequestCacheMiddleware`: If a request is cacheable the cache is asked first and only if no response is found the request is passed down the middleware chain. The cache lifetime and tags are determined from the `X-FullPageCache-Enabled`, `X-FullPageCache-Lifetime` and `X-FullPageCache-Tags` that are set by upstream middlewares - or controllers. + or controllers. Additionally the middleware adds `ETag` and `CacheControl` Headers taking the lifetime and setting + `maxPublicCacheTime` into account. -- `fullPageCachePublicHeader` : Set `ETag` and `CacheControl` Headers based on the `X-FullPageCache-Enabled` and the - `X-FullPageCache-Lifetime` headers taking the `maxPublicCacheTime` into account. - -- `fullPageCacheFusionAutoconfiguration`: Connects to the fusion cache and extracts tags plus the allowed lifetime which is then +- `FusionAutoconfigurationMiddleware`: Connects to the fusion cache and extracts tags plus the allowed lifetime which is then stored in the response headers `X-FullPageCache-Enabled`, `X-FullPageCache-Lifetime` and `X-FullPageCache-Tags`. - This component expects a header `X-FullPageCache-EnableFusionAutoconfiguration` which is set automatically for `Neos.Neos:Page`. + This component is only active if the header `X-FullPageCache-EnableFusionAutoconfiguration` is present in the response + which is set automatically for `Neos.Neos:Page`. Custom controllers that want to control the caching behavior directly can set the headers `X-FullPageCache-Enabled`, `X-FullPageCache-Lifetime` and `X-FullPageCache-Tags` directly while fusion based controllers can enable the autoconfiguration From d7e9df019da6e9e9dac76294abd976f35c0cae4c Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Sun, 31 Oct 2021 10:56:54 +0100 Subject: [PATCH 5/5] Update README.md Co-authored-by: Alexander Berl --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index df6ded7..c450107 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ The package defines two http middlewares: - `RequestCacheMiddleware`: If a request is cacheable the cache is asked first and only if no response is found the request is passed down the middleware chain. The cache lifetime and tags are determined from the `X-FullPageCache-Enabled`, `X-FullPageCache-Lifetime` and `X-FullPageCache-Tags` that are set by upstream middlewares - or controllers. Additionally the middleware adds `ETag` and `CacheControl` Headers taking the lifetime and setting + or controllers. Additionally the middleware adds `ETag` and `Cache-Control` Headers taking the lifetime and setting `maxPublicCacheTime` into account. - `FusionAutoconfigurationMiddleware`: Connects to the fusion cache and extracts tags plus the allowed lifetime which is then