Skip to content

Commit 2a84f6c

Browse files
committed
TASK: Use custom header X-CacheLifetime to communicate with the cache headers
This allows to store caches with lifetime 0 (indefinite) in the cache since we can invalidate them anytime but still send a maximal public lifetime of a day. Also this makes it possible to disable the sending of public cache headers
1 parent b860ee1 commit 2a84f6c

4 files changed

Lines changed: 79 additions & 53 deletions

File tree

Classes/Http/CacheControlHeaderComponent.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public function handle(ComponentContext $componentContext)
6868

6969
if ($tags) {
7070
$modifiedResponse = $response
71-
->withHeader('CacheControl', 's-maxage=' . ($lifetime ?? 86400))
71+
->withHeader('X-CacheLifetime', $lifetime)
7272
->withHeader('X-CacheTags', $tags);
7373

7474
$componentContext->replaceHttpResponse($modifiedResponse);

Classes/Http/RequestInterceptorComponent.php

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
<?php
22
namespace Flowpack\FullPageCache\Http;
33

4-
use GuzzleHttp\Psr7\Response;
54
use Neos\Flow\Annotations as Flow;
65
use Neos\Cache\Frontend\StringFrontend;
76
use Neos\Flow\Http\Component\ComponentChain;
87
use Neos\Flow\Http\Component\ComponentContext;
98
use Neos\Flow\Http\Component\ComponentInterface;
109
use Neos\Flow\Security\SessionDataContainer;
1110
use Neos\Flow\Session\SessionManagerInterface;
11+
use GuzzleHttp\Psr7\Response;
1212
use function GuzzleHttp\Psr7\parse_response;
1313

1414
/**
@@ -28,6 +28,12 @@ class RequestInterceptorComponent implements ComponentInterface
2828
*/
2929
protected $enabled;
3030

31+
/**
32+
* @var boolean
33+
* @Flow\InjectConfiguration(path="maxPublicCacheTime")
34+
*/
35+
protected $maxPublicCacheTime;
36+
3137
/**
3238
* @Flow\Inject(lazy=false)
3339
* @var SessionManagerInterface
@@ -75,32 +81,46 @@ public function handle(ComponentContext $componentContext)
7581
$etag = $cachedResponse->getHeaderLine('ETag');
7682
$lifetime = (int)$cachedResponse->getHeaderLine('X-Storage-Lifetime');
7783
$timestamp = (int)$cachedResponse->getHeaderLine('X-Storage-Timestamp');
78-
$age = time() - $timestamp;
79-
80-
if ($age > $lifetime) {
81-
return;
82-
}
8384

85+
// return 304 not modified when possible
8486
$ifNoneMatch = $request->getHeaderLine('If-None-Match');
85-
if ($ifNoneMatch && $ifNoneMatch === $etag ) {
87+
if ($ifNoneMatch && $ifNoneMatch === $etag ) {
8688
if (class_exists('Neos\\Flow\\Http\\Response')) {
87-
$response = new \Neos\Flow\Http\Response();
89+
$notModifiedResponse = new \Neos\Flow\Http\Response();
8890
} else {
89-
$response = new Response(304);
91+
$notModifiedResponse = new Response();
9092
}
91-
$response = $response
92-
->withHeader('CacheControl', 'max-age=' . ($lifetime - $age))
93-
->withHeader('X-From-FullPageCache', $entryIdentifier);
94-
} else {
95-
$response = $cachedResponse
96-
->withoutHeader('X-Storage-Lifetime')
97-
->withoutHeader('X-Storage-Timestamp')
98-
->withoutHeader('CacheControl')
99-
->withHeader('CacheControl', 'max-age=' . ($lifetime - $age))
93+
$notModifiedResponse = $notModifiedResponse
94+
->withStatus(304)
10095
->withHeader('X-From-FullPageCache', $entryIdentifier);
96+
97+
$componentContext->replaceHttpResponse($notModifiedResponse);
98+
$componentContext->setParameter(ComponentChain::class, 'cancel', true);
99+
return;
100+
}
101+
102+
$cachedResponse = $cachedResponse
103+
->withoutHeader('X-Storage-Lifetime')
104+
->withoutHeader('X-Storage-Timestamp')
105+
->withHeader('X-From-FullPageCache', $entryIdentifier);
106+
107+
if ($this->maxPublicCacheTime > 0) {
108+
if ($lifetime > 0) {
109+
$remainingCacheTime = $lifetime - (time() - $timestamp);
110+
if ($remainingCacheTime > $this->maxPublicCacheTime) {
111+
$remainingCacheTime = $this->maxPublicCacheTime;
112+
}
113+
if ($remainingCacheTime > 0) {
114+
$cachedResponse = $cachedResponse
115+
->withHeader('CacheControl', 'max-age=' . $remainingCacheTime);
116+
}
117+
} else {
118+
$cachedResponse = $cachedResponse
119+
->withHeader('CacheControl', 'max-age=' . $this->maxPublicCacheTime);
120+
}
101121
}
102122

103-
$componentContext->replaceHttpResponse($response);
123+
$componentContext->replaceHttpResponse($cachedResponse);
104124
$componentContext->setParameter(ComponentChain::class, 'cancel', true);
105125
}
106126
}

Classes/Http/RequestStorageComponent.php

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ class RequestStorageComponent implements ComponentInterface
2424
*/
2525
protected $enabled;
2626

27+
/**
28+
* @var boolean
29+
* @Flow\InjectConfiguration(path="maxPublicCacheTime")
30+
*/
31+
protected $maxPublicCacheTime;
32+
2733
/**
2834
* @inheritDoc
2935
*/
@@ -36,44 +42,39 @@ public function handle(ComponentContext $componentContext)
3642
return;
3743
}
3844

39-
if ($response->hasHeader('CacheControl')) {
40-
$cacheControl = $response->getHeaderLine('CacheControl');
41-
if ($cacheControl && strpos($cacheControl,'s-maxage=') === 0) {
42-
$lifetime = (int) substr($cacheControl, 10);
43-
$cacheTags = $response->getHeader('X-CacheTags') ;
45+
if ($response->hasHeader('X-CacheLifetime')) {
46+
$lifetime = (int)$response->getHeaderLine('X-CacheLifetime');
47+
$cacheTags = $response->getHeader('X-CacheTags') ;
48+
$entryIdentifier = md5((string)$request->getUri());
49+
50+
$publicLifetime = 0;
51+
if ($this->maxPublicCacheTime > 0) {
52+
if ($lifetime > 0 && $lifetime < $this->maxPublicCacheTime) {
53+
$publicLifetime = $lifetime;
54+
} else {
55+
$publicLifetime = $this->maxPublicCacheTime;
56+
}
57+
}
4458

45-
$entryIdentifier = md5((string)$request->getUri());
46-
$etag = md5(str($response));
59+
$modifiedResponse = $response
60+
->withoutHeader('X-CacheTags')
61+
->withoutHeader('X-CacheLifetime');
4762

48-
$modifiedResponse = $response
49-
->withoutHeader('X-CacheTags')
50-
->withoutHeader('CacheControl')
51-
->withAddedHeader('ETag', $etag)
52-
->withAddedHeader('CacheControl', 'max-age=' . $lifetime);
63+
if ($publicLifetime > 0) {
64+
$entryContentHash = md5(str($response));
65+
$modifiedResponse = $modifiedResponse
66+
->withAddedHeader('ETag', $entryContentHash)
67+
->withAddedHeader('CacheControl', 'max-age=' . $publicLifetime);
68+
}
5369

54-
$modifiedResponseforStorage = $modifiedResponse
55-
->withHeader('X-Storage-Component', $entryIdentifier)
56-
->withHeader('X-Storage-Timestamp', time())
57-
->withHeader('X-Storage-Lifetime', $lifetime);
70+
$modifiedResponseforStorage = $modifiedResponse
71+
->withHeader('X-Storage-Timestamp', time())
72+
->withHeader('X-Storage-Lifetime', $lifetime);
5873

59-
$this->cacheFrontend->set($entryIdentifier, str($modifiedResponseforStorage), $cacheTags, $lifetime);
60-
$response->getBody()->rewind();
74+
$this->cacheFrontend->set($entryIdentifier, str($modifiedResponseforStorage), $cacheTags, $lifetime);
6175

62-
$ifNoneMatch = $request->getHeaderLine('If-None-Match');
63-
if ($ifNoneMatch && $ifNoneMatch === $etag ) {
64-
if (class_exists('Neos\\Flow\\Http\\Response')) {
65-
$notModifiedResponse = new \Neos\Flow\Http\Response();
66-
} else {
67-
$notModifiedResponse = new Response(304);
68-
}
69-
$notModifiedResponse = $notModifiedResponse
70-
->withAddedHeader('CacheControl', 'max-age=' . $lifetime)
71-
->withHeader('X-From-FullPageCache', $entryIdentifier);
72-
$componentContext->replaceHttpResponse($notModifiedResponse);
73-
} else {
74-
$componentContext->replaceHttpResponse($modifiedResponse);
75-
}
76-
}
76+
$modifiedResponse->getBody()->rewind();
77+
$componentContext->replaceHttpResponse($modifiedResponse);
7778
}
7879
}
7980
}

Configuration/Settings.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
Flowpack:
22
FullPageCache:
3+
# enable full page caching
34
enabled: true
5+
6+
# the maximum public cache control header sent
7+
# set to 0 if you do not want to send public CacheControl headers
8+
maxPublicCacheTime: 86400
49
Neos:
510
Flow:
611
http:

0 commit comments

Comments
 (0)