diff --git a/src/CacheMiddleware.php b/src/CacheMiddleware.php index fa439c8..be52e1f 100644 --- a/src/CacheMiddleware.php +++ b/src/CacheMiddleware.php @@ -164,25 +164,28 @@ function (ResponseInterface $response) use ($request) { return new FulfilledPromise( $cacheEntry->getResponse()->withHeader(static::HEADER_CACHE_INFO, static::HEADER_CACHE_HIT) ); - } elseif ($staleResponse - || ($maxStaleCache !== null && $cacheEntry->getStaleAge() <= $maxStaleCache) + } elseif ($cacheEntry->staleWhileValidate() + && (($maxStaleCache === null) || ($staleResponse || $cacheEntry->getStaleAge() <= $maxStaleCache)) ) { - // Staled cache! + /* + * The cached response indicated that it may be served stale while background revalidation (or fetch) + * occurs, and the client did not limit maximum staleness. (https://tools.ietf.org/html/rfc5861#section-3) + * + * Return the cached, stale response; initiate deferred revalidation/re-fetch. + */ + static::addReValidationRequest( + static::getRequestWithReValidationHeader($request, $cacheEntry), + $this->cacheStorage, + $cacheEntry + ); + return new FulfilledPromise( - $cacheEntry->getResponse()->withHeader(static::HEADER_CACHE_INFO, static::HEADER_CACHE_HIT) + $cacheEntry->getResponse() + ->withHeader(self::HEADER_CACHE_INFO, self::HEADER_CACHE_STALE) ); - } elseif ($cacheEntry->hasValidationInformation() && !$onlyFromCache) { + } elseif (($cacheEntry->staleWhileValidate() && $cacheEntry->getStaleAge() <= $maxStaleCache) || ($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(static::HEADER_CACHE_INFO, static::HEADER_CACHE_STALE) - ); - } } } else { $cacheEntry = null; diff --git a/tests/RequestCacheControlTest.php b/tests/RequestCacheControlTest.php index 5dbdcc9..247e94e 100644 --- a/tests/RequestCacheControlTest.php +++ b/tests/RequestCacheControlTest.php @@ -42,6 +42,11 @@ protected function setUp(): void (new Response()) ->withAddedHeader('Cache-Control', 'max-age=3') ); + case '/1s-stale-while-revalidate': + return new FulfilledPromise( + (new Response()) + ->withAddedHeader('Cache-Control', 'max-age=1, stale-while-revalidate=3') + ); } throw new \InvalidArgumentException(); @@ -162,6 +167,41 @@ public function testMaxStaleHeader() CacheMiddleware::HEADER_CACHE_MISS, $response->getHeaderLine(CacheMiddleware::HEADER_CACHE_INFO) ); + + $response = $this->client->get('http://test.com/1s-stale-while-revalidate', [ + 'headers' => [ + 'Cache-Control' => 'max-stale', + ] + ]); + $this->assertEquals( + CacheMiddleware::HEADER_CACHE_MISS, + $response->getHeaderLine(CacheMiddleware::HEADER_CACHE_INFO) + ); + + sleep(2); + + $response = $this->client->get('http://test.com/1s-stale-while-revalidate', [ + 'headers' => [ + 'Cache-Control' => 'max-stale=1', + ] + ]); + $this->assertEquals( + CacheMiddleware::HEADER_CACHE_STALE, + $response->getHeaderLine(CacheMiddleware::HEADER_CACHE_INFO) + ); + + sleep(1); + + $response = $this->client->get('http://test.com/1s-stale-while-revalidate', [ + 'headers' => [ + 'Cache-Control' => 'max-stale=1', + ] + ]); + $this->assertEquals( + CacheMiddleware::HEADER_CACHE_MISS, + $response->getHeaderLine(CacheMiddleware::HEADER_CACHE_INFO) + ); + } public function testMinFreshHeader() diff --git a/tests/ResponseCacheControlTest.php b/tests/ResponseCacheControlTest.php index 79e3353..fe62810 100644 --- a/tests/ResponseCacheControlTest.php +++ b/tests/ResponseCacheControlTest.php @@ -30,7 +30,7 @@ protected function setUp(): void case '/2s-complex': return new FulfilledPromise( (new Response()) - ->withAddedHeader('Cache-Control', 'invalid-token="yes", max-age=2, stale-while-revalidate=60') + ->withAddedHeader('Cache-Control', 'invalid-token="yes", max-age=2, stale-while-revalidate=3') ); case '/no-store': return new FulfilledPromise( @@ -91,6 +91,11 @@ public function testMaxAgeComplexHeader() sleep(3); + $response = $this->client->get('http://test.com/2s-complex'); + $this->assertEquals(CacheMiddleware::HEADER_CACHE_STALE, $response->getHeaderLine(CacheMiddleware::HEADER_CACHE_INFO)); + + sleep(5); + $response = $this->client->get('http://test.com/2s-complex'); $this->assertEquals(CacheMiddleware::HEADER_CACHE_MISS, $response->getHeaderLine(CacheMiddleware::HEADER_CACHE_INFO)); }