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) {
$body = $cacheEntry->getResponse()->getBody();
if ($body->tell() > 0) {
$body->rewind();
}
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;
}
}
$update = false;
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());
// Merge headers of the "304 Not Modified" and the cache entry
foreach ($cacheEntry->getResponse()->getHeaders() as $headerName => $headerValue) {
if (!$response->hasHeader($headerName)) {
$response = $response->withHeader($headerName, $headerValue);
}
}
$update = true;
} else {
$response = $response->withHeader(self::HEADER_CACHE_INFO, self::HEADER_CACHE_MISS);
}
return self::addToCache($this->cacheStorage, $request, $response, $update);
}, function (\Exception $ex) use($cacheEntry) {
if ($ex instanceof TransferException) {
$response = static::getStaleResponse($cacheEntry);
if ($response instanceof ResponseInterface) {
return $response;
}
}
throw $ex;
});
};
}