/** * @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; }); }; }