Skip to content

Commit a7b788c

Browse files
authored
Merge b275498 into ff042d2
2 parents ff042d2 + b275498 commit a7b788c

File tree

1 file changed

+109
-7
lines changed

1 file changed

+109
-7
lines changed

src/CachePlugin.php

Lines changed: 109 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;
@@ -57,7 +58,6 @@ public function __construct(CacheItemPoolInterface $pool, StreamFactory $streamF
5758
public function handleRequest(RequestInterface $request, callable $next, callable $first)
5859
{
5960
$method = strtoupper($request->getMethod());
60-
6161
// if the request not is cachable, move to $next
6262
if ($method !== 'GET' && $method !== 'HEAD') {
6363
return $next($request);
@@ -68,15 +68,47 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
6868
$cacheItem = $this->pool->getItem($key);
6969

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

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

95+
7996
return $next($request)->then(function (ResponseInterface $response) use ($cacheItem) {
97+
if (304 === $response->getStatusCode()) {
98+
if (!$cacheItem->isHit()) {
99+
// We do not have the item in cache. We can return the cached response.
100+
return $response;
101+
}
102+
103+
// The cached response we have is still valid
104+
$data = $cacheItem->get();
105+
$data['expiresAt'] = time() + $this->getMaxAge($response);
106+
$cacheItem->set($data)->expiresAfter($this->config['cache_lifetime'] + $data['expiresAt']);
107+
$this->pool->save($cacheItem);
108+
109+
return $this->createResponseFromCacheItem($cacheItem);
110+
}
111+
80112
if ($this->isCacheable($response)) {
81113
$bodyStream = $response->getBody();
82114
$body = $bodyStream->__toString();
@@ -86,8 +118,16 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
86118
$response = $response->withBody($this->streamFactory->createStream($body));
87119
}
88120

89-
$cacheItem->set(['response' => $response, 'body' => $body])
90-
->expiresAfter($this->getMaxAge($response));
121+
$expiresAt = time() + $this->getMaxAge($response);
122+
$cacheItem
123+
->expiresAfter($this->config['cache_lifetime'] + $expiresAt)
124+
->set([
125+
'response' => $response,
126+
'body' => $body,
127+
'expiresAt' => $expiresAt,
128+
'createdAt' => time(),
129+
'etag' => $response->getHeader('ETag'),
130+
]);
91131
$this->pool->save($cacheItem);
92132
}
93133

@@ -194,11 +234,73 @@ private function getMaxAge(ResponseInterface $response)
194234
private function configureOptions(OptionsResolver $resolver)
195235
{
196236
$resolver->setDefaults([
237+
'cache_lifetime' => 2592000, // 30 days
197238
'default_ttl' => null,
198239
'respect_cache_headers' => true,
199240
]);
200241

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

0 commit comments

Comments
 (0)