Skip to content

Commit 824b9aa

Browse files
committed
Added support for Last-Modified and ETag
1 parent acc6599 commit 824b9aa

File tree

1 file changed

+103
-7
lines changed

1 file changed

+103
-7
lines changed

src/CachePlugin.php

Lines changed: 103 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Http\Client\Common\Plugin;
66
use Http\Message\StreamFactory;
77
use Http\Promise\FulfilledPromise;
8+
use Psr\Cache\CacheItemInterface;
89
use Psr\Cache\CacheItemPoolInterface;
910
use Psr\Http\Message\RequestInterface;
1011
use Psr\Http\Message\ResponseInterface;
@@ -58,7 +59,6 @@ public function __construct(CacheItemPoolInterface $pool, StreamFactory $streamF
5859
public function handleRequest(RequestInterface $request, callable $next, callable $first)
5960
{
6061
$method = strtoupper($request->getMethod());
61-
6262
// if the request not is cachable, move to $next
6363
if ($method !== 'GET' && $method !== 'HEAD') {
6464
return $next($request);
@@ -69,15 +69,42 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
6969
$cacheItem = $this->pool->getItem($key);
7070

7171
if ($cacheItem->isHit()) {
72-
// return cached response
7372
$data = $cacheItem->get();
74-
$response = $data['response'];
75-
$response = $response->withBody($this->streamFactory->createStream($data['body']));
73+
if (isset($data['expiresAt']) && time() > $data['expiresAt']) {
74+
// This item is still valid according to previous cache headers
75+
return new FulfilledPromise($this->createResponseFromCacheItem($cacheItem));
76+
}
77+
78+
// Add headers to ask the server if this cache is still valid
79+
if ($modifiedAt = $this->getModifiedAt($cacheItem)) {
80+
$modifiedAt = new \DateTime('@'.$modifiedAt);
81+
$modifiedAt->setTimezone(new \DateTimeZone('GMT'));
82+
$request = $request->withHeader(
83+
'If-Modified-Since',
84+
sprintf('%s GMT', $modifiedAt->format('l, d-M-y H:i:s'))
85+
);
86+
}
7687

77-
return new FulfilledPromise($response);
88+
if ($etag = $this->getETag($cacheItem)) {
89+
$request = $request->withHeader(
90+
'If-None-Match',
91+
$etag
92+
);
93+
}
7894
}
7995

96+
8097
return $next($request)->then(function (ResponseInterface $response) use ($cacheItem) {
98+
if (304 === $response->getStatusCode()) {
99+
// The cached response we have is still valid
100+
$data = $cacheItem->get();
101+
$data['expiresAt'] = time() + $this->getMaxAge($response);
102+
$cacheItem->set($data)->expiresAfter($this->config['cache_lifetime']);
103+
$this->pool->save($cacheItem);
104+
105+
return $this->createResponseFromCacheItem($cacheItem);
106+
}
107+
81108
if ($this->isCacheable($response)) {
82109
$bodyStream = $response->getBody();
83110
$body = $bodyStream->__toString();
@@ -87,8 +114,15 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
87114
$response = $response->withBody($this->streamFactory->createStream($body));
88115
}
89116

90-
$cacheItem->set(['response' => $response, 'body' => $body])
91-
->expiresAfter($this->getMaxAge($response));
117+
$cacheItem
118+
->expiresAfter($this->config['cache_lifetime'])
119+
->set([
120+
'response' => $response,
121+
'body' => $body,
122+
'expiresAt' => time() + $this->getMaxAge($response),
123+
'createdAt' => time(),
124+
'etag' => $response->getHeader('ETag'),
125+
]);
92126
$this->pool->save($cacheItem);
93127
}
94128

@@ -195,13 +229,75 @@ private function getMaxAge(ResponseInterface $response)
195229
private function configureOptions(OptionsResolver $resolver)
196230
{
197231
$resolver->setDefaults([
232+
'cache_lifetime' => 2592000, // 30 days
198233
'default_ttl' => null,
199234
'respect_cache_headers' => true,
200235
'hash_algo' => 'sha1',
201236
]);
202237

238+
$resolver->setAllowedTypes('cache_lifetime', 'int');
203239
$resolver->setAllowedTypes('default_ttl', ['int', 'null']);
204240
$resolver->setAllowedTypes('respect_cache_headers', 'bool');
205241
$resolver->setAllowedValues('hash_algo', hash_algos());
206242
}
243+
244+
/**
245+
* @param CacheItemInterface $cacheItem
246+
*
247+
* @return ResponseInterface
248+
*/
249+
private function createResponseFromCacheItem(CacheItemInterface $cacheItem)
250+
{
251+
$data = $cacheItem->get();
252+
253+
/** @var ResponseInterface $response */
254+
$response = $data['response'];
255+
$response = $response->withBody($this->streamFactory->createStream($data['body']));
256+
257+
return $response;
258+
}
259+
260+
/**
261+
* Get the timestamp when the cached response was stored.
262+
*
263+
* @param CacheItemInterface $cacheItem
264+
*
265+
* @return int|null
266+
*/
267+
private function getModifiedAt(CacheItemInterface $cacheItem)
268+
{
269+
$data = $cacheItem->get();
270+
if (!isset($data['createdAt'])) {
271+
return null;
272+
}
273+
274+
return $data['createdAt'];
275+
}
276+
277+
/**
278+
* Get the ETag from the cached response.
279+
*
280+
* @param CacheItemInterface $cacheItem
281+
*
282+
* @return string|null
283+
*/
284+
private function getETag(CacheItemInterface $cacheItem)
285+
{
286+
$data = $cacheItem->get();
287+
if (!isset($data['etag'])) {
288+
return null;
289+
}
290+
291+
if (is_array($data['etag'])) {
292+
foreach($data['etag'] as $etag) {
293+
if (!empty($etag)) {
294+
return $etag;
295+
}
296+
}
297+
298+
return null;
299+
}
300+
301+
return $data['etag'];
302+
}
207303
}

0 commit comments

Comments
 (0)