/** * @param ResponseInterface $response * @return CacheEntry|null entry to save, null if can't cache it */ protected function getCacheObject(ResponseInterface $response) { if ($response->hasHeader("Cache-Control")) { $values = new KeyValueHttpHeader($response->getHeader("Cache-Control")); if ($values->has('no-store')) { // No store allowed (maybe some sensitives data...) return null; } if ($values->has('no-cache')) { // Stale response see RFC7234 section 5.2.1.4 $entry = new CacheEntry($response, new \DateTime('-1 seconds')); return $entry->hasValidationInformation() ? $entry : null; } if ($values->has('max-age')) { return new CacheEntry($response, new \DateTime('+' . $values->get('max-age') . 'seconds')); } return new CacheEntry($response, new \DateTime()); } if ($response->hasHeader("Expires")) { $expireAt = \DateTime::createFromFormat('D, d M Y H:i:s T', $response->getHeaderLine("Expires")); if ($expireAt !== FALSE) { return new CacheEntry($response, $expireAt); } } return new CacheEntry($response, new \DateTime('-1 seconds')); }
/** * @param ResponseInterface $response * @param \DateTime $staleAt * @param \DateTime|null $staleIfErrorTo if null, detected with the headers (RFC 5861) * @param \DateTime $staleWhileRevalidateTo */ public function __construct(ResponseInterface $response, \DateTime $staleAt, \DateTime $staleIfErrorTo = null, \DateTime $staleWhileRevalidateTo = null) { $this->response = $response; $this->staleAt = $staleAt; $values = new KeyValueHttpHeader($response->getHeader("Cache-Control")); if ($staleIfErrorTo === null && $values->has('stale-if-error')) { $this->staleIfErrorTo = (new \DateTime())->setTimestamp($this->staleAt->getTimestamp() + (int) $values->get('stale-if-error')); } else { $this->staleIfErrorTo = $staleIfErrorTo; } if ($staleWhileRevalidateTo === null && $values->has('stale-while-revalidate')) { $this->staleWhileRevalidateTo = new \DateTime('+' . $values->get('stale-while-revalidate') . 'seconds'); } else { $this->staleWhileRevalidateTo = $staleWhileRevalidateTo; } }
/** * @param ResponseInterface $response * @param KeyValueHttpHeader $cacheControl * @return CacheEntry|null */ protected function getCacheObjectForCacheControl(ResponseInterface $response, KeyValueHttpHeader $cacheControl) { if ($cacheControl->has('no-store')) { // No store allowed (maybe some sensitives data...) return null; } if ($cacheControl->has('no-cache')) { // Stale response see RFC7234 section 5.2.1.4 $entry = new CacheEntry($response, new \DateTime('-1 seconds')); return $entry->hasValidationInformation() ? $entry : null; } if ($cacheControl->has('max-age')) { return new CacheEntry($response, new \DateTime('+' . (int) $cacheControl->get('max-age') . 'seconds')); } return new CacheEntry($response, new \DateTime()); }
/** * {@inheritdoc} */ protected function getCacheObject(RequestInterface $request, ResponseInterface $response) { $cacheControl = new KeyValueHttpHeader($response->getHeader('Cache-Control')); if ($cacheControl->has('private')) { return; } return parent::getCacheObject($request, $response); }
/** * @param RequestInterface $request * @param ResponseInterface $response * @param \DateTime $staleAt * @param \DateTime|null $staleIfErrorTo if null, detected with the headers (RFC 5861) * @param \DateTime|null $staleWhileRevalidateTo */ public function __construct(RequestInterface $request, ResponseInterface $response, \DateTime $staleAt, \DateTime $staleIfErrorTo = null, \DateTime $staleWhileRevalidateTo = null) { $this->dateCreated = new \DateTime(); $this->request = $request; $this->response = $response; $this->staleAt = $staleAt; $values = new KeyValueHttpHeader($response->getHeader('Cache-Control')); if ($staleIfErrorTo === null && $values->has('stale-if-error')) { $this->staleIfErrorTo = new \DateTime('@' . ($this->staleAt->getTimestamp() + (int) $values->get('stale-if-error'))); } else { $this->staleIfErrorTo = $staleIfErrorTo; } if ($staleWhileRevalidateTo === null && $values->has('stale-while-revalidate')) { $this->staleWhileRevalidateTo = new \DateTime('@' . ($this->staleAt->getTimestamp() + (int) $values->get('stale-while-revalidate'))); } else { $this->staleWhileRevalidateTo = $staleWhileRevalidateTo; } }
public function testBase() { $response = new Response(200, ['Cache-Control' => ['max-age=120', ' stale-while-revalidate=60 ', ' private ', 'zero=0', 'nothing=', 'false=false', 'with-comma=1,yeah="2"']]); $values = new KeyValueHttpHeader($response->getHeader('Cache-Control')); $this->assertTrue($values->has('max-age')); $this->assertTrue($values->has('stale-while-revalidate')); $this->assertTrue($values->has('private')); $this->assertTrue($values->has('zero')); $this->assertTrue($values->has('nothing')); $this->assertTrue($values->has('false')); $this->assertTrue($values->has('with-comma')); $this->assertTrue($values->has('yeah')); $this->assertEquals(120, $values->get('max-age')); $this->assertEquals(60, $values->get('stale-while-revalidate')); $this->assertEquals(0, $values->get('zero')); $this->assertEquals('', $values->get('nothing')); $this->assertEquals('false', $values->get('false')); $this->assertEquals(1, $values->get('with-comma')); $this->assertEquals(2, $values->get('yeah')); }
/** * @param RequestInterface $request * @param ResponseInterface $response * * @return bool true if success */ public function cache(RequestInterface $request, ResponseInterface $response) { $reqCacheControl = new KeyValueHttpHeader($request->getHeader('Cache-Control')); if ($reqCacheControl->has('no-store')) { // No caching allowed return false; } $cacheObject = $this->getCacheObject($request, $response); if ($cacheObject !== null) { return $this->storage->save($this->getCacheKey($request), $cacheObject); } return false; }
/** * @param callable $handler * * @return callable */ public function __invoke(callable $handler) { return function (RequestInterface $request, array $options) use(&$handler) { if (!isset($this->httpMethods[strtoupper($request->getMethod())])) { // No caching for this method allowed return $handler($request, $options)->then(function (ResponseInterface $response) { return $response->withHeader(self::HEADER_CACHE_INFO, self::HEADER_CACHE_MISS); }); } if ($request->hasHeader(self::HEADER_RE_VALIDATION)) { // It's a re-validation request, so bypass the cache! return $handler($request->withoutHeader(self::HEADER_RE_VALIDATION), $options); } // Retrieve information from request (Cache-Control) $reqCacheControl = new KeyValueHttpHeader($request->getHeader('Cache-Control')); $onlyFromCache = $reqCacheControl->has('only-if-cached'); $staleResponse = $reqCacheControl->has('max-stale') && $reqCacheControl->get('max-stale') === ''; $maxStaleCache = $reqCacheControl->get('max-stale', null); $minFreshCache = $reqCacheControl->get('min-fresh', null); // If cache => return new FulfilledPromise(...) with response $cacheEntry = $this->cacheStorage->fetch($request); if ($cacheEntry instanceof CacheEntry) { if ($cacheEntry->isFresh() && ($minFreshCache === null || $cacheEntry->getStaleAge() + (int) $minFreshCache <= 0)) { // Cache HIT! return new FulfilledPromise($cacheEntry->getResponse()->withHeader(self::HEADER_CACHE_INFO, self::HEADER_CACHE_HIT)); } elseif ($staleResponse || $maxStaleCache !== null && $cacheEntry->getStaleAge() <= $maxStaleCache) { // Staled cache! return new FulfilledPromise($cacheEntry->getResponse()->withHeader(self::HEADER_CACHE_INFO, self::HEADER_CACHE_HIT)); } elseif ($cacheEntry->hasValidationInformation() && !$onlyFromCache) { // Re-validation header $request = static::getRequestWithReValidationHeader($request, $cacheEntry); if ($cacheEntry->staleWhileValidate()) { static::addReValidationRequest($request, $this->cacheStorage, $cacheEntry); return new FulfilledPromise($cacheEntry->getResponse()->withHeader(self::HEADER_CACHE_INFO, self::HEADER_CACHE_STALE)); } } } else { $cacheEntry = null; } if ($cacheEntry === null && $onlyFromCache) { // Explicit asking of a cached response => 504 return new FulfilledPromise(new Response(504)); } /** @var Promise $promise */ $promise = $handler($request, $options); return $promise->then(function (ResponseInterface $response) use($request, $cacheEntry) { // Check if error and looking for a staled content if ($response->getStatusCode() >= 500) { $responseStale = static::getStaleResponse($cacheEntry); if ($responseStale instanceof ResponseInterface) { return $responseStale; } } if ($response->getStatusCode() == 304 && $cacheEntry instanceof CacheEntry) { // Not modified => cache entry is re-validate /** @var ResponseInterface $response */ $response = $response->withStatus($cacheEntry->getResponse()->getStatusCode())->withHeader(self::HEADER_CACHE_INFO, self::HEADER_CACHE_HIT); $response = $response->withBody($cacheEntry->getResponse()->getBody()); } else { $response = $response->withHeader(self::HEADER_CACHE_INFO, self::HEADER_CACHE_MISS); } // Add to the cache $this->cacheStorage->cache($request, $response); return $response; }, function (\Exception $ex) use($cacheEntry) { if ($ex instanceof TransferException) { $response = static::getStaleResponse($cacheEntry); if ($response instanceof ResponseInterface) { return $response; } } throw $ex; }); }; }