@@ -1274,6 +1274,175 @@ public function testEsiCacheSendsTheLowestTtlForHeadRequests()
1274
1274
$ this ->assertEquals (100 , $ this ->response ->getTtl ());
1275
1275
}
1276
1276
1277
+ public function testEsiCacheIncludesEmbeddedResponseContentWhenMainResponseFailsRevalidationAndEmbeddedResponseIsFresh ()
1278
+ {
1279
+ $ this ->setNextResponses ([
1280
+ [
1281
+ 'status ' => 200 ,
1282
+ 'body ' => 'main <esi:include src="/foo" /> ' ,
1283
+ 'headers ' => [
1284
+ 'Cache-Control ' => 's-maxage=0 ' , // goes stale immediately
1285
+ 'Surrogate-Control ' => 'content="ESI/1.0" ' ,
1286
+ 'Last-Modified ' => 'Mon, 12 Aug 2024 10:00:00 +0000 ' ,
1287
+ ],
1288
+ ],
1289
+ [
1290
+ 'status ' => 200 ,
1291
+ 'body ' => 'embedded ' ,
1292
+ 'headers ' => [
1293
+ 'Cache-Control ' => 's-maxage=10 ' , // stays fresh
1294
+ 'Last-Modified ' => 'Mon, 12 Aug 2024 10:05:00 +0000 ' ,
1295
+ ]
1296
+ ],
1297
+ ]);
1298
+
1299
+ // prime the cache
1300
+ $ this ->request ('GET ' , '/ ' , [], [], true );
1301
+ $ this ->assertSame (200 , $ this ->response ->getStatusCode ());
1302
+ $ this ->assertSame ('main embedded ' , $ this ->response ->getContent ());
1303
+ $ this ->assertSame ('Mon, 12 Aug 2024 10:05:00 +0000 ' , $ this ->response ->getLastModified ()->format (\DATE_RFC2822 )); // max of both values
1304
+
1305
+ $ this ->setNextResponses ([
1306
+ [
1307
+ // On the next request, the main response has an updated Last-Modified (main page was modified)...
1308
+ 'status ' => 200 ,
1309
+ 'body ' => 'main <esi:include src="/foo" /> ' ,
1310
+ 'headers ' => [
1311
+ 'Cache-Control ' => 's-maxage=0 ' ,
1312
+ 'Surrogate-Control ' => 'content="ESI/1.0" ' ,
1313
+ 'Last-Modified ' => 'Mon, 12 Aug 2024 10:10:00 +0000 ' ,
1314
+ ],
1315
+ ],
1316
+ // no revalidation request happens for the embedded response, since it is still fresh
1317
+ ]);
1318
+
1319
+ // Re-request with Last-Modified time that we received when the cache was primed
1320
+ $ this ->request ('GET ' , '/ ' , ['HTTP_IF_MODIFIED_SINCE ' => 'Mon, 12 Aug 2024 10:05:00 +0000 ' ], [], true );
1321
+
1322
+ $ this ->assertSame (200 , $ this ->response ->getStatusCode ());
1323
+
1324
+ // The cache should use the content ("embedded") from the cached entry
1325
+ $ this ->assertSame ('main embedded ' , $ this ->response ->getContent ());
1326
+
1327
+ $ traces = $ this ->cache ->getTraces ();
1328
+ $ this ->assertSame (['stale ' , 'invalid ' , 'store ' ], $ traces ['GET / ' ]);
1329
+
1330
+ // The embedded resource was still fresh
1331
+ $ this ->assertSame (['fresh ' ], $ traces ['GET /foo ' ]);
1332
+ }
1333
+
1334
+ public function testEsiCacheIncludesEmbeddedResponseContentWhenMainResponseFailsRevalidationAndEmbeddedResponseIsValid ()
1335
+ {
1336
+ $ this ->setNextResponses ([
1337
+ [
1338
+ 'status ' => 200 ,
1339
+ 'body ' => 'main <esi:include src="/foo" /> ' ,
1340
+ 'headers ' => [
1341
+ 'Cache-Control ' => 's-maxage=0 ' , // goes stale immediately
1342
+ 'Surrogate-Control ' => 'content="ESI/1.0" ' ,
1343
+ 'Last-Modified ' => 'Mon, 12 Aug 2024 10:00:00 +0000 ' ,
1344
+ ],
1345
+ ],
1346
+ [
1347
+ 'status ' => 200 ,
1348
+ 'body ' => 'embedded ' ,
1349
+ 'headers ' => [
1350
+ 'Cache-Control ' => 's-maxage=0 ' , // goes stale immediately
1351
+ 'Last-Modified ' => 'Mon, 12 Aug 2024 10:05:00 +0000 ' ,
1352
+ ]
1353
+ ],
1354
+ ]);
1355
+
1356
+ // prime the cache
1357
+ $ this ->request ('GET ' , '/ ' , [], [], true );
1358
+ $ this ->assertSame (200 , $ this ->response ->getStatusCode ());
1359
+ $ this ->assertSame ('main embedded ' , $ this ->response ->getContent ());
1360
+ $ this ->assertSame ('Mon, 12 Aug 2024 10:05:00 +0000 ' , $ this ->response ->getLastModified ()->format (\DATE_RFC2822 )); // max of both values
1361
+
1362
+ $ this ->setNextResponses ([
1363
+ [
1364
+ // On the next request, the main response has an updated Last-Modified (main page was modified)...
1365
+ 'status ' => 200 ,
1366
+ 'body ' => 'main <esi:include src="/foo" /> ' ,
1367
+ 'headers ' => [
1368
+ 'Cache-Control ' => 's-maxage=0 ' ,
1369
+ 'Surrogate-Control ' => 'content="ESI/1.0" ' ,
1370
+ 'Last-Modified ' => 'Mon, 12 Aug 2024 10:10:00 +0000 ' ,
1371
+ ],
1372
+ ],
1373
+ [
1374
+ // We have a stale cache entry for the embedded response which will be revalidated.
1375
+ // Let's assume the resource did not change, so the controller sends a 304 without content body.
1376
+ 'status ' => 304 ,
1377
+ 'body ' => '' ,
1378
+ 'headers ' => [
1379
+ 'Cache-Control ' => 's-maxage=0 ' ,
1380
+ ],
1381
+ ],
1382
+ ]);
1383
+
1384
+ // Re-request with Last-Modified time that we received when the cache was primed
1385
+ $ this ->request ('GET ' , '/ ' , ['HTTP_IF_MODIFIED_SINCE ' => 'Mon, 12 Aug 2024 10:05:00 +0000 ' ], [], true );
1386
+
1387
+ $ this ->assertSame (200 , $ this ->response ->getStatusCode ());
1388
+
1389
+ // The cache should use the content ("embedded") from the cached entry
1390
+ $ this ->assertSame ('main embedded ' , $ this ->response ->getContent ());
1391
+
1392
+ $ traces = $ this ->cache ->getTraces ();
1393
+ $ this ->assertSame (['stale ' , 'invalid ' , 'store ' ], $ traces ['GET / ' ]);
1394
+
1395
+ // Check that the embedded resource was successfully revalidated
1396
+ $ this ->assertSame (['stale ' , 'valid ' , 'store ' ], $ traces ['GET /foo ' ]);
1397
+ }
1398
+
1399
+ public function testEsiCacheIncludesEmbeddedResponseContentWhenMainAndEmbeddedResponseAreFresh ()
1400
+ {
1401
+ $ this ->setNextResponses ([
1402
+ [
1403
+ 'status ' => 200 ,
1404
+ 'body ' => 'main <esi:include src="/foo" /> ' ,
1405
+ 'headers ' => [
1406
+ 'Cache-Control ' => 's-maxage=10 ' ,
1407
+ 'Surrogate-Control ' => 'content="ESI/1.0" ' ,
1408
+ 'Last-Modified ' => 'Mon, 12 Aug 2024 10:05:00 +0000 ' ,
1409
+ ],
1410
+ ],
1411
+ [
1412
+ 'status ' => 200 ,
1413
+ 'body ' => 'embedded ' ,
1414
+ 'headers ' => [
1415
+ 'Cache-Control ' => 's-maxage=10 ' ,
1416
+ 'Last-Modified ' => 'Mon, 12 Aug 2024 10:00:00 +0000 ' ,
1417
+ ]
1418
+ ],
1419
+ ]);
1420
+
1421
+ // prime the cache
1422
+ $ this ->request ('GET ' , '/ ' , [], [], true );
1423
+ $ this ->assertSame (200 , $ this ->response ->getStatusCode ());
1424
+ $ this ->assertSame ('main embedded ' , $ this ->response ->getContent ());
1425
+ $ this ->assertSame ('Mon, 12 Aug 2024 10:05:00 +0000 ' , $ this ->response ->getLastModified ()->format (\DATE_RFC2822 ));
1426
+
1427
+ // Assume that a client received 'Mon, 12 Aug 2024 10:00:00 +0000' as last-modified information in the past. This may, for example,
1428
+ // be the case when the "main" response at that point had an older Last-Modified time, so the embedded response's Last-Modified time
1429
+ // governed the result for the combined response. In other words, the client received a Last-Modified time that still validates the
1430
+ // embedded response as of now, but no longer matches the Last-Modified time of the "main" resource.
1431
+ // Now this client does a revalidation request.
1432
+ $ this ->request ('GET ' , '/ ' , ['HTTP_IF_MODIFIED_SINCE ' => 'Mon, 12 Aug 2024 10:00:00 +0000 ' ], [], true );
1433
+
1434
+ $ this ->assertSame (200 , $ this ->response ->getStatusCode ());
1435
+
1436
+ // The cache should use the content ("embedded") from the cached entry
1437
+ $ this ->assertSame ('main embedded ' , $ this ->response ->getContent ());
1438
+
1439
+ $ traces = $ this ->cache ->getTraces ();
1440
+ $ this ->assertSame (['fresh ' ], $ traces ['GET / ' ]);
1441
+
1442
+ // Check that the embedded resource was successfully revalidated
1443
+ $ this ->assertSame (['fresh ' ], $ traces ['GET /foo ' ]);
1444
+ }
1445
+
1277
1446
public function testEsiCacheForceValidation ()
1278
1447
{
1279
1448
$ responses = [
0 commit comments