diff --git a/HttpCache/HttpCache.php b/HttpCache/HttpCache.php index 184666978c..6ffe889057 100644 --- a/HttpCache/HttpCache.php +++ b/HttpCache/HttpCache.php @@ -237,7 +237,9 @@ public function handle(Request $request, int $type = HttpKernelInterface::MAIN_R $response->prepare($request); - $response->isNotModified($request); + if (HttpKernelInterface::MAIN_REQUEST === $type) { + $response->isNotModified($request); + } return $response; } diff --git a/Tests/HttpCache/HttpCacheTest.php b/Tests/HttpCache/HttpCacheTest.php index 1f63686053..915af2f35f 100644 --- a/Tests/HttpCache/HttpCacheTest.php +++ b/Tests/HttpCache/HttpCacheTest.php @@ -1329,6 +1329,175 @@ public function testEsiCacheSendsTheLowestTtlForHeadRequests() $this->assertEquals(100, $this->response->getTtl()); } + public function testEsiCacheIncludesEmbeddedResponseContentWhenMainResponseFailsRevalidationAndEmbeddedResponseIsFresh() + { + $this->setNextResponses([ + [ + 'status' => 200, + 'body' => 'main ', + 'headers' => [ + 'Cache-Control' => 's-maxage=0', // goes stale immediately + 'Surrogate-Control' => 'content="ESI/1.0"', + 'Last-Modified' => 'Mon, 12 Aug 2024 10:00:00 +0000', + ], + ], + [ + 'status' => 200, + 'body' => 'embedded', + 'headers' => [ + 'Cache-Control' => 's-maxage=10', // stays fresh + 'Last-Modified' => 'Mon, 12 Aug 2024 10:05:00 +0000', + ] + ], + ]); + + // prime the cache + $this->request('GET', '/', [], [], true); + $this->assertSame(200, $this->response->getStatusCode()); + $this->assertSame('main embedded', $this->response->getContent()); + $this->assertSame('Mon, 12 Aug 2024 10:05:00 +0000', $this->response->getLastModified()->format(\DATE_RFC2822)); // max of both values + + $this->setNextResponses([ + [ + // On the next request, the main response has an updated Last-Modified (main page was modified)... + 'status' => 200, + 'body' => 'main ', + 'headers' => [ + 'Cache-Control' => 's-maxage=0', + 'Surrogate-Control' => 'content="ESI/1.0"', + 'Last-Modified' => 'Mon, 12 Aug 2024 10:10:00 +0000', + ], + ], + // no revalidation request happens for the embedded response, since it is still fresh + ]); + + // Re-request with Last-Modified time that we received when the cache was primed + $this->request('GET', '/', ['HTTP_IF_MODIFIED_SINCE' => 'Mon, 12 Aug 2024 10:05:00 +0000'], [], true); + + $this->assertSame(200, $this->response->getStatusCode()); + + // The cache should use the content ("embedded") from the cached entry + $this->assertSame('main embedded', $this->response->getContent()); + + $traces = $this->cache->getTraces(); + $this->assertSame(['stale', 'invalid', 'store'], $traces['GET /']); + + // The embedded resource was still fresh + $this->assertSame(['fresh'], $traces['GET /foo']); + } + + public function testEsiCacheIncludesEmbeddedResponseContentWhenMainResponseFailsRevalidationAndEmbeddedResponseIsValid() + { + $this->setNextResponses([ + [ + 'status' => 200, + 'body' => 'main ', + 'headers' => [ + 'Cache-Control' => 's-maxage=0', // goes stale immediately + 'Surrogate-Control' => 'content="ESI/1.0"', + 'Last-Modified' => 'Mon, 12 Aug 2024 10:00:00 +0000', + ], + ], + [ + 'status' => 200, + 'body' => 'embedded', + 'headers' => [ + 'Cache-Control' => 's-maxage=0', // goes stale immediately + 'Last-Modified' => 'Mon, 12 Aug 2024 10:05:00 +0000', + ] + ], + ]); + + // prime the cache + $this->request('GET', '/', [], [], true); + $this->assertSame(200, $this->response->getStatusCode()); + $this->assertSame('main embedded', $this->response->getContent()); + $this->assertSame('Mon, 12 Aug 2024 10:05:00 +0000', $this->response->getLastModified()->format(\DATE_RFC2822)); // max of both values + + $this->setNextResponses([ + [ + // On the next request, the main response has an updated Last-Modified (main page was modified)... + 'status' => 200, + 'body' => 'main ', + 'headers' => [ + 'Cache-Control' => 's-maxage=0', + 'Surrogate-Control' => 'content="ESI/1.0"', + 'Last-Modified' => 'Mon, 12 Aug 2024 10:10:00 +0000', + ], + ], + [ + // We have a stale cache entry for the embedded response which will be revalidated. + // Let's assume the resource did not change, so the controller sends a 304 without content body. + 'status' => 304, + 'body' => '', + 'headers' => [ + 'Cache-Control' => 's-maxage=0', + ], + ], + ]); + + // Re-request with Last-Modified time that we received when the cache was primed + $this->request('GET', '/', ['HTTP_IF_MODIFIED_SINCE' => 'Mon, 12 Aug 2024 10:05:00 +0000'], [], true); + + $this->assertSame(200, $this->response->getStatusCode()); + + // The cache should use the content ("embedded") from the cached entry + $this->assertSame('main embedded', $this->response->getContent()); + + $traces = $this->cache->getTraces(); + $this->assertSame(['stale', 'invalid', 'store'], $traces['GET /']); + + // Check that the embedded resource was successfully revalidated + $this->assertSame(['stale', 'valid', 'store'], $traces['GET /foo']); + } + + public function testEsiCacheIncludesEmbeddedResponseContentWhenMainAndEmbeddedResponseAreFresh() + { + $this->setNextResponses([ + [ + 'status' => 200, + 'body' => 'main ', + 'headers' => [ + 'Cache-Control' => 's-maxage=10', + 'Surrogate-Control' => 'content="ESI/1.0"', + 'Last-Modified' => 'Mon, 12 Aug 2024 10:05:00 +0000', + ], + ], + [ + 'status' => 200, + 'body' => 'embedded', + 'headers' => [ + 'Cache-Control' => 's-maxage=10', + 'Last-Modified' => 'Mon, 12 Aug 2024 10:00:00 +0000', + ] + ], + ]); + + // prime the cache + $this->request('GET', '/', [], [], true); + $this->assertSame(200, $this->response->getStatusCode()); + $this->assertSame('main embedded', $this->response->getContent()); + $this->assertSame('Mon, 12 Aug 2024 10:05:00 +0000', $this->response->getLastModified()->format(\DATE_RFC2822)); + + // Assume that a client received 'Mon, 12 Aug 2024 10:00:00 +0000' as last-modified information in the past. This may, for example, + // be the case when the "main" response at that point had an older Last-Modified time, so the embedded response's Last-Modified time + // governed the result for the combined response. In other words, the client received a Last-Modified time that still validates the + // embedded response as of now, but no longer matches the Last-Modified time of the "main" resource. + // Now this client does a revalidation request. + $this->request('GET', '/', ['HTTP_IF_MODIFIED_SINCE' => 'Mon, 12 Aug 2024 10:00:00 +0000'], [], true); + + $this->assertSame(200, $this->response->getStatusCode()); + + // The cache should use the content ("embedded") from the cached entry + $this->assertSame('main embedded', $this->response->getContent()); + + $traces = $this->cache->getTraces(); + $this->assertSame(['fresh'], $traces['GET /']); + + // Check that the embedded resource was successfully revalidated + $this->assertSame(['fresh'], $traces['GET /foo']); + } + public function testEsiCacheForceValidation() { $responses = [