bug #25583 [HttpKernel] Call Response->setPrivate() instead of sending raw header() when session is started (Toflar)
This PR was merged into the 3.4 branch.
Discussion
----------
[HttpKernel] Call Response->setPrivate() instead of sending raw header() when session is started
| Q | A
| ------------- | ---
| Branch? | 3.4
| Bug fix? | yes
| New feature? | no
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | https://github.com/symfony/symfony/issues/24988
| License | MIT
| Doc PR | -
As described in #24988 I think the current handling of the `Cache-Control` header set by the `NativeSessionStorage` causes inconsistent behaviour.
In #24988 @nicolas-grekas states that if you start a session a response should be considered to be private. I do agree with this but up until now, nobody takes care of this on `kernel.response`.
I think we must always suppress the `NativeSessionStorage` from generating any headers by default. Otherwise the `Cache-Control` header never makes it to the `Response` instance and is thus missed by `kernel.response` listeners and for example the Symfony HttpCache. So depending on whether you use Symfony's HttpCache or Varnish as a reverse proxy, caching would be handled differently. Varnish would consider the response to be private if you set the php.ini setting `session.cache_limiter` to `nocache` (which is default) because it will receive the header. HttpCache would not because the `Cache-Control` header is not present on the `Response`. That's inconsistent and may cause confusion or problems when switching proxies.
Commits
-------
dbc1c1c4b6
[HttpKernel] Call Response->setPrivate() instead of sending raw header() when session is started
This commit is contained in:
commit
f0b1dc2f7e
@ -825,7 +825,7 @@ class FrameworkExtension extends Extension
|
|||||||
|
|
||||||
// session storage
|
// session storage
|
||||||
$container->setAlias('session.storage', $config['storage_id'])->setPrivate(true);
|
$container->setAlias('session.storage', $config['storage_id'])->setPrivate(true);
|
||||||
$options = array();
|
$options = array('cache_limiter' => '0');
|
||||||
foreach (array('name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'use_cookies', 'gc_maxlifetime', 'gc_probability', 'gc_divisor', 'use_strict_mode') as $key) {
|
foreach (array('name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'use_cookies', 'gc_maxlifetime', 'gc_probability', 'gc_divisor', 'use_strict_mode') as $key) {
|
||||||
if (isset($config[$key])) {
|
if (isset($config[$key])) {
|
||||||
$options[$key] = $config[$key];
|
$options[$key] = $config[$key];
|
||||||
|
@ -53,6 +53,10 @@ class SaveSessionListener implements EventSubscriberInterface
|
|||||||
$session = $event->getRequest()->getSession();
|
$session = $event->getRequest()->getSession();
|
||||||
if ($session && $session->isStarted()) {
|
if ($session && $session->isStarted()) {
|
||||||
$session->save();
|
$session->save();
|
||||||
|
$event->getResponse()
|
||||||
|
->setPrivate()
|
||||||
|
->setMaxAge(0)
|
||||||
|
->headers->addCacheControlDirective('must-revalidate');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpKernel\Tests\EventListener;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||||
|
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
|
||||||
|
use Symfony\Component\HttpKernel\EventListener\SaveSessionListener;
|
||||||
|
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||||
|
|
||||||
|
class SaveSessionListenerTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testOnlyTriggeredOnMasterRequest()
|
||||||
|
{
|
||||||
|
$listener = new SaveSessionListener();
|
||||||
|
$event = $this->getMockBuilder(FilterResponseEvent::class)->disableOriginalConstructor()->getMock();
|
||||||
|
$event->expects($this->once())->method('isMasterRequest')->willReturn(false);
|
||||||
|
$event->expects($this->never())->method('getRequest');
|
||||||
|
|
||||||
|
// sub request
|
||||||
|
$listener->onKernelResponse($event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSessionSavedAndResponsePrivate()
|
||||||
|
{
|
||||||
|
$listener = new SaveSessionListener();
|
||||||
|
$kernel = $this->getMockBuilder(HttpKernelInterface::class)->disableOriginalConstructor()->getMock();
|
||||||
|
|
||||||
|
$session = $this->getMockBuilder(SessionInterface::class)->disableOriginalConstructor()->getMock();
|
||||||
|
$session->expects($this->once())->method('isStarted')->willReturn(true);
|
||||||
|
$session->expects($this->once())->method('save');
|
||||||
|
|
||||||
|
$request = new Request();
|
||||||
|
$request->setSession($session);
|
||||||
|
$response = new Response();
|
||||||
|
$listener->onKernelResponse(new FilterResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response));
|
||||||
|
|
||||||
|
$this->assertTrue($response->headers->hasCacheControlDirective('private'));
|
||||||
|
$this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate'));
|
||||||
|
$this->assertSame('0', $response->headers->getCacheControlDirective('max-age'));
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user