@@ -1329,6 +1329,175 @@ public function testEsiCacheSendsTheLowestTtlForHeadRequests()
1329
1329
$ this ->assertEquals (100 , $ this ->response ->getTtl ());
1330
1330
}
1331
1331
1332
+ public function testEsiCacheIncludesEmbeddedResponseContentWhenMainResponseFailsRevalidationAndEmbeddedResponseIsFresh ()
1333
+ {
1334
+ $ this ->setNextResponses ([
1335
+ [
1336
+ 'status ' => 200 ,
1337
+ 'body ' => 'main <esi:include src="/foo" /> ' ,
1338
+ 'headers ' => [
1339
+ 'Cache-Control ' => 's-maxage=0 ' , // goes stale immediately
1340
+ 'Surrogate-Control ' => 'content="ESI/1.0" ' ,
1341
+ 'Last-Modified ' => 'Mon, 12 Aug 2024 10:00:00 +0000 ' ,
1342
+ ],
1343
+ ],
1344
+ [
1345
+ 'status ' => 200 ,
1346
+ 'body ' => 'embedded ' ,
1347
+ 'headers ' => [
1348
+ 'Cache-Control ' => 's-maxage=10 ' , // stays fresh
1349
+ 'Last-Modified ' => 'Mon, 12 Aug 2024 10:05:00 +0000 ' ,
1350
+ ]
1351
+ ],
1352
+ ]);
1353
+
1354
+ // prime the cache
1355
+ $ this ->request ('GET ' , '/ ' , [], [], true );
1356
+ $ this ->assertSame (200 , $ this ->response ->getStatusCode ());
1357
+ $ this ->assertSame ('main embedded ' , $ this ->response ->getContent ());
1358
+ $ this ->assertSame ('Mon, 12 Aug 2024 10:05:00 +0000 ' , $ this ->response ->getLastModified ()->format (\DATE_RFC2822 )); // max of both values
1359
+
1360
+ $ this ->setNextResponses ([
1361
+ [
1362
+ // On the next request, the main response has an updated Last-Modified (main page was modified)...
1363
+ 'status ' => 200 ,
1364
+ 'body ' => 'main <esi:include src="/foo" /> ' ,
1365
+ 'headers ' => [
1366
+ 'Cache-Control ' => 's-maxage=0 ' ,
1367
+ 'Surrogate-Control ' => 'content="ESI/1.0" ' ,
1368
+ 'Last-Modified ' => 'Mon, 12 Aug 2024 10:10:00 +0000 ' ,
1369
+ ],
1370
+ ],
1371
+ // no revalidation request happens for the embedded response, since it is still fresh
1372
+ ]);
1373
+
1374
+ // Re-request with Last-Modified time that we received when the cache was primed
1375
+ $ this ->request ('GET ' , '/ ' , ['HTTP_IF_MODIFIED_SINCE ' => 'Mon, 12 Aug 2024 10:05:00 +0000 ' ], [], true );
1376
+
1377
+ $ this ->assertSame (200 , $ this ->response ->getStatusCode ());
1378
+
1379
+ // The cache should use the content ("embedded") from the cached entry
1380
+ $ this ->assertSame ('main embedded ' , $ this ->response ->getContent ());
1381
+
1382
+ $ traces = $ this ->cache ->getTraces ();
1383
+ $ this ->assertSame (['stale ' , 'invalid ' , 'store ' ], $ traces ['GET / ' ]);
1384
+
1385
+ // The embedded resource was still fresh
1386
+ $ this ->assertSame (['fresh ' ], $ traces ['GET /foo ' ]);
1387
+ }
1388
+
1389
+ public function testEsiCacheIncludesEmbeddedResponseContentWhenMainResponseFailsRevalidationAndEmbeddedResponseIsValid ()
1390
+ {
1391
+ $ this ->setNextResponses ([
1392
+ [
1393
+ 'status ' => 200 ,
1394
+ 'body ' => 'main <esi:include src="/foo" /> ' ,
1395
+ 'headers ' => [
1396
+ 'Cache-Control ' => 's-maxage=0 ' , // goes stale immediately
1397
+ 'Surrogate-Control ' => 'content="ESI/1.0" ' ,
1398
+ 'Last-Modified ' => 'Mon, 12 Aug 2024 10:00:00 +0000 ' ,
1399
+ ],
1400
+ ],
1401
+ [
1402
+ 'status ' => 200 ,
1403
+ 'body ' => 'embedded ' ,
1404
+ 'headers ' => [
1405
+ 'Cache-Control ' => 's-maxage=0 ' , // goes stale immediately
1406
+ 'Last-Modified ' => 'Mon, 12 Aug 2024 10:05:00 +0000 ' ,
1407
+ ]
1408
+ ],
1409
+ ]);
1410
+
1411
+ // prime the cache
1412
+ $ this ->request ('GET ' , '/ ' , [], [], true );
1413
+ $ this ->assertSame (200 , $ this ->response ->getStatusCode ());
1414
+ $ this ->assertSame ('main embedded ' , $ this ->response ->getContent ());
1415
+ $ this ->assertSame ('Mon, 12 Aug 2024 10:05:00 +0000 ' , $ this ->response ->getLastModified ()->format (\DATE_RFC2822 )); // max of both values
1416
+
1417
+ $ this ->setNextResponses ([
1418
+ [
1419
+ // On the next request, the main response has an updated Last-Modified (main page was modified)...
1420
+ 'status ' => 200 ,
1421
+ 'body ' => 'main <esi:include src="/foo" /> ' ,
1422
+ 'headers ' => [
1423
+ 'Cache-Control ' => 's-maxage=0 ' ,
1424
+ 'Surrogate-Control ' => 'content="ESI/1.0" ' ,
1425
+ 'Last-Modified ' => 'Mon, 12 Aug 2024 10:10:00 +0000 ' ,
1426
+ ],
1427
+ ],
1428
+ [
1429
+ // We have a stale cache entry for the embedded response which will be revalidated.
1430
+ // Let's assume the resource did not change, so the controller sends a 304 without content body.
1431
+ 'status ' => 304 ,
1432
+ 'body ' => '' ,
1433
+ 'headers ' => [
1434
+ 'Cache-Control ' => 's-maxage=0 ' ,
1435
+ ],
1436
+ ],
1437
+ ]);
1438
+
1439
+ // Re-request with Last-Modified time that we received when the cache was primed
1440
+ $ this ->request ('GET ' , '/ ' , ['HTTP_IF_MODIFIED_SINCE ' => 'Mon, 12 Aug 2024 10:05:00 +0000 ' ], [], true );
1441
+
1442
+ $ this ->assertSame (200 , $ this ->response ->getStatusCode ());
1443
+
1444
+ // The cache should use the content ("embedded") from the cached entry
1445
+ $ this ->assertSame ('main embedded ' , $ this ->response ->getContent ());
1446
+
1447
+ $ traces = $ this ->cache ->getTraces ();
1448
+ $ this ->assertSame (['stale ' , 'invalid ' , 'store ' ], $ traces ['GET / ' ]);
1449
+
1450
+ // Check that the embedded resource was successfully revalidated
1451
+ $ this ->assertSame (['stale ' , 'valid ' , 'store ' ], $ traces ['GET /foo ' ]);
1452
+ }
1453
+
1454
+ public function testEsiCacheIncludesEmbeddedResponseContentWhenMainAndEmbeddedResponseAreFresh ()
1455
+ {
1456
+ $ this ->setNextResponses ([
1457
+ [
1458
+ 'status ' => 200 ,
1459
+ 'body ' => 'main <esi:include src="/foo" /> ' ,
1460
+ 'headers ' => [
1461
+ 'Cache-Control ' => 's-maxage=10 ' ,
1462
+ 'Surrogate-Control ' => 'content="ESI/1.0" ' ,
1463
+ 'Last-Modified ' => 'Mon, 12 Aug 2024 10:05:00 +0000 ' ,
1464
+ ],
1465
+ ],
1466
+ [
1467
+ 'status ' => 200 ,
1468
+ 'body ' => 'embedded ' ,
1469
+ 'headers ' => [
1470
+ 'Cache-Control ' => 's-maxage=10 ' ,
1471
+ 'Last-Modified ' => 'Mon, 12 Aug 2024 10:00:00 +0000 ' ,
1472
+ ]
1473
+ ],
1474
+ ]);
1475
+
1476
+ // prime the cache
1477
+ $ this ->request ('GET ' , '/ ' , [], [], true );
1478
+ $ this ->assertSame (200 , $ this ->response ->getStatusCode ());
1479
+ $ this ->assertSame ('main embedded ' , $ this ->response ->getContent ());
1480
+ $ this ->assertSame ('Mon, 12 Aug 2024 10:05:00 +0000 ' , $ this ->response ->getLastModified ()->format (\DATE_RFC2822 ));
1481
+
1482
+ // Assume that a client received 'Mon, 12 Aug 2024 10:00:00 +0000' as last-modified information in the past. This may, for example,
1483
+ // be the case when the "main" response at that point had an older Last-Modified time, so the embedded response's Last-Modified time
1484
+ // governed the result for the combined response. In other words, the client received a Last-Modified time that still validates the
1485
+ // embedded response as of now, but no longer matches the Last-Modified time of the "main" resource.
1486
+ // Now this client does a revalidation request.
1487
+ $ this ->request ('GET ' , '/ ' , ['HTTP_IF_MODIFIED_SINCE ' => 'Mon, 12 Aug 2024 10:00:00 +0000 ' ], [], true );
1488
+
1489
+ $ this ->assertSame (200 , $ this ->response ->getStatusCode ());
1490
+
1491
+ // The cache should use the content ("embedded") from the cached entry
1492
+ $ this ->assertSame ('main embedded ' , $ this ->response ->getContent ());
1493
+
1494
+ $ traces = $ this ->cache ->getTraces ();
1495
+ $ this ->assertSame (['fresh ' ], $ traces ['GET / ' ]);
1496
+
1497
+ // Check that the embedded resource was successfully revalidated
1498
+ $ this ->assertSame (['fresh ' ], $ traces ['GET /foo ' ]);
1499
+ }
1500
+
1332
1501
public function testEsiCacheForceValidation ()
1333
1502
{
1334
1503
$ responses = [
0 commit comments