[HttpKernel] Added response cache-control modification if page is composed of ESIs.
Rules are : - If one of the ESI has validation cache strategy, the whole page will be forced to validate. - In none of the ESI has validation, the response will feature a Cache-Control directive with s-maxage value equals to the smallest TTL of ESIs.
This commit is contained in:
parent
2201382fa1
commit
bebdcb242d
@ -31,6 +31,7 @@ class HttpCache implements HttpKernelInterface
|
||||
protected $store;
|
||||
protected $request;
|
||||
protected $esi;
|
||||
protected $esiTtls;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@ -136,6 +137,7 @@ class HttpCache implements HttpKernelInterface
|
||||
if (HttpKernelInterface::MASTER_REQUEST === $type) {
|
||||
$this->traces = array();
|
||||
$this->request = $request;
|
||||
$this->esiTtls = array();
|
||||
}
|
||||
|
||||
$path = $request->getPathInfo();
|
||||
@ -160,9 +162,43 @@ class HttpCache implements HttpKernelInterface
|
||||
$response->headers->set('X-Symfony-Cache', $this->getLog());
|
||||
}
|
||||
|
||||
if (null !== $this->esi) {
|
||||
$this->addEsiTtl($response);
|
||||
|
||||
if ($request === $this->request) {
|
||||
$this->updateResponseCacheControl($response);
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the response's TTL locally.
|
||||
*
|
||||
* @param Response $response
|
||||
*/
|
||||
protected function addEsiTtl(Response $response)
|
||||
{
|
||||
$this->esiTtls[] = $response->isValidateable() ? 0 : $response->getTtl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the master response TTL to the smallest TTL received or force validation if
|
||||
* one of the ESI has validation cache strategy.
|
||||
*
|
||||
* @param Response $response
|
||||
*/
|
||||
protected function updateResponseCacheControl(Response $response)
|
||||
{
|
||||
$ttl = min($this->esiTtls);
|
||||
if (0 === $ttl) {
|
||||
$response->headers->set('Cache-Control', 'no-cache, must-revalidate');
|
||||
} else {
|
||||
$response->setSharedMaxAge($ttl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards the Request to the backend without storing the Response in the cache.
|
||||
*
|
||||
|
@ -893,4 +893,67 @@ class HttpCacheTest extends HttpCacheTestCase
|
||||
|
||||
$this->assertExceptionsAreNotCaught();
|
||||
}
|
||||
|
||||
public function testEsiCacheSendsTheLowestTtl()
|
||||
{
|
||||
$responses = array(
|
||||
array(
|
||||
'status' => 200,
|
||||
'body' => '<esi:include src="/foo" /> <esi:include src="/bar" />',
|
||||
'headers' => array(
|
||||
'Cache-Control' => 's-maxage=300',
|
||||
'Surrogate-Control' => 'content="ESI/1.0"',
|
||||
),
|
||||
),
|
||||
array(
|
||||
'status' => 200,
|
||||
'body' => 'Hello World!',
|
||||
'headers' => array('Cache-Control' => 's-maxage=300'),
|
||||
),
|
||||
array(
|
||||
'status' => 200,
|
||||
'body' => 'My name is Bobby.',
|
||||
'headers' => array('Cache-Control' => 's-maxage=100'),
|
||||
),
|
||||
);
|
||||
|
||||
$this->setNextResponses($responses);
|
||||
|
||||
$this->request('GET', '/', array(), array(), true);
|
||||
$this->assertEquals("Hello World! My name is Bobby.", $this->response->getContent());
|
||||
$this->assertEquals(100, $this->response->getTtl());
|
||||
}
|
||||
|
||||
public function testEsiCacheForceValidation()
|
||||
{
|
||||
$responses = array(
|
||||
array(
|
||||
'status' => 200,
|
||||
'body' => '<esi:include src="/foo" /> <esi:include src="/bar" />',
|
||||
'headers' => array(
|
||||
'Cache-Control' => 's-maxage=300',
|
||||
'Surrogate-Control' => 'content="ESI/1.0"',
|
||||
),
|
||||
),
|
||||
array(
|
||||
'status' => 200,
|
||||
'body' => 'Hello World!',
|
||||
'headers' => array('ETag' => 'foobar'),
|
||||
),
|
||||
array(
|
||||
'status' => 200,
|
||||
'body' => 'My name is Bobby.',
|
||||
'headers' => array('Cache-Control' => 's-maxage=100'),
|
||||
),
|
||||
);
|
||||
|
||||
$this->setNextResponses($responses);
|
||||
|
||||
$this->request('GET', '/', array(), array(), true);
|
||||
$this->assertEquals('Hello World! My name is Bobby.', $this->response->getContent());
|
||||
$this->assertEquals(null, $this->response->getTtl());
|
||||
$this->assertTrue($this->response->mustRevalidate());
|
||||
$this->assertTrue($this->response->headers->hasCacheControlDirective('private'));
|
||||
$this->assertTrue($this->response->headers->hasCacheControlDirective('no-cache'));
|
||||
}
|
||||
}
|
||||
|
@ -12,8 +12,10 @@
|
||||
namespace Symfony\Tests\Component\HttpKernel\HttpCache;
|
||||
|
||||
require_once __DIR__.'/TestHttpKernel.php';
|
||||
require_once __DIR__.'/TestMultipleHttpKernel.php';
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\HttpCache\Esi;
|
||||
use Symfony\Component\HttpKernel\HttpCache\HttpCache;
|
||||
use Symfony\Component\HttpKernel\HttpCache\Store;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
@ -28,12 +30,14 @@ class HttpCacheTestCase extends \PHPUnit_Framework_TestCase
|
||||
protected $response;
|
||||
protected $responses;
|
||||
protected $catch;
|
||||
protected $esi;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->kernel = null;
|
||||
|
||||
$this->cache = null;
|
||||
$this->esi = null;
|
||||
$this->caches = array();
|
||||
$this->cacheConfig = array();
|
||||
|
||||
@ -41,6 +45,7 @@ class HttpCacheTestCase extends \PHPUnit_Framework_TestCase
|
||||
$this->response = null;
|
||||
$this->responses = array();
|
||||
|
||||
|
||||
$this->catch = false;
|
||||
|
||||
$this->clearDirectory(sys_get_temp_dir().'/http_cache');
|
||||
@ -101,7 +106,7 @@ class HttpCacheTestCase extends \PHPUnit_Framework_TestCase
|
||||
$this->assertFalse($this->kernel->isCatchingExceptions());
|
||||
}
|
||||
|
||||
public function request($method, $uri = '/', $server = array(), $cookies = array())
|
||||
public function request($method, $uri = '/', $server = array(), $cookies = array(), $esi = false)
|
||||
{
|
||||
if (null === $this->kernel) {
|
||||
throw new \LogicException('You must call setNextResponse() before calling request().');
|
||||
@ -112,7 +117,9 @@ class HttpCacheTestCase extends \PHPUnit_Framework_TestCase
|
||||
$this->store = new Store(sys_get_temp_dir().'/http_cache');
|
||||
|
||||
$this->cacheConfig['debug'] = true;
|
||||
$this->cache = new HttpCache($this->kernel, $this->store, null, $this->cacheConfig);
|
||||
|
||||
$this->esi = $esi ? new Esi() : null;
|
||||
$this->cache = new HttpCache($this->kernel, $this->store, $this->esi, $this->cacheConfig);
|
||||
$this->request = Request::create($uri, $method, array(), $cookies, array(), $server);
|
||||
|
||||
$this->response = $this->cache->handle($this->request, HttpKernelInterface::MASTER_REQUEST, $this->catch);
|
||||
@ -133,11 +140,14 @@ class HttpCacheTestCase extends \PHPUnit_Framework_TestCase
|
||||
// A basic response with 200 status code and a tiny body.
|
||||
public function setNextResponse($statusCode = 200, array $headers = array(), $body = 'Hello World', \Closure $customizer = null)
|
||||
{
|
||||
$called = false;
|
||||
|
||||
$this->kernel = new TestHttpKernel($body, $statusCode, $headers, $customizer);
|
||||
}
|
||||
|
||||
public function setNextResponses($responses)
|
||||
{
|
||||
$this->kernel = new TestMultipleHttpKernel($responses);
|
||||
}
|
||||
|
||||
public function catchExceptions($catch = true)
|
||||
{
|
||||
$this->catch = $catch;
|
||||
|
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Tests\Component\HttpKernel\HttpCache;
|
||||
|
||||
use Symfony\Component\HttpKernel\HttpKernel;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
|
||||
|
||||
class TestMultipleHttpKernel extends HttpKernel implements ControllerResolverInterface
|
||||
{
|
||||
protected $bodies;
|
||||
protected $statuses;
|
||||
protected $headers;
|
||||
protected $catch;
|
||||
protected $call;
|
||||
|
||||
public function __construct($responses)
|
||||
{
|
||||
$this->bodies = array();
|
||||
$this->statuses = array();
|
||||
$this->headers = array();
|
||||
$this->call = false;
|
||||
|
||||
foreach ($responses as $response) {
|
||||
$this->bodies[] = $response['body'];
|
||||
$this->statuses[] = $response['status'];
|
||||
$this->headers[] = $response['headers'];
|
||||
}
|
||||
|
||||
parent::__construct(new EventDispatcher(), $this);
|
||||
}
|
||||
|
||||
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = false)
|
||||
{
|
||||
return parent::handle($request, $type, $catch);
|
||||
}
|
||||
|
||||
public function getController(Request $request)
|
||||
{
|
||||
return array($this, 'callController');
|
||||
}
|
||||
|
||||
public function getArguments(Request $request, $controller)
|
||||
{
|
||||
return array($request);
|
||||
}
|
||||
|
||||
public function callController(Request $request)
|
||||
{
|
||||
$this->called = true;
|
||||
|
||||
$response = new Response(array_shift($this->bodies), array_shift($this->statuses), array_shift($this->headers));
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function hasBeenCalled()
|
||||
{
|
||||
return $this->called;
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->call = false;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user