[HttpKernel] Deprecate X-Status-Code for better alternative

This marks the X-Status-Code header method of setting a custom response
status code in exception listeners as deprecated. Instead there is now
a new method on the GetResponseForExceptionEvent that allows successful
status codes in the response sent to the client.
This commit is contained in:
James Halsall 2016-09-02 10:40:33 +01:00 committed by James Halsall
parent 3e9b8f3ba5
commit cc0ef282cd
No known key found for this signature in database
GPG Key ID: 40958F5830D67B2C
12 changed files with 114 additions and 14 deletions

View File

@ -76,6 +76,11 @@ HttpKernel
* The `Psr6CacheClearer::addPool()` method has been deprecated. Pass an array of pools indexed
by name to the constructor instead.
* The `X-Status-Code` header method of setting a custom status code in the response
when handling exceptions has been removed. There is now a new
`GetResponseForExceptionEvent::allowCustomResponseCode()` method instead, which
will tell the Kernel to use the response code set on the event's response object.
Process
-------

View File

@ -236,6 +236,11 @@ HttpKernel
* The `Psr6CacheClearer::addPool()` method has been removed. Pass an array of pools indexed
by name to the constructor instead.
* The `X-Status-Code` header method of setting a custom status code in the response
when handling exceptions has been removed. There is now a new
`GetResponseForExceptionEvent::allowCustomResponseCode()` method instead, which
will tell the Kernel to use the response code set on the event's response object.
Process
-------

View File

@ -36,6 +36,11 @@ class GetResponseForExceptionEvent extends GetResponseEvent
*/
private $exception;
/**
* @var bool
*/
private $allowCustomResponseCode = false;
public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, \Exception $e)
{
parent::__construct($kernel, $request, $requestType);
@ -64,4 +69,22 @@ class GetResponseForExceptionEvent extends GetResponseEvent
{
$this->exception = $exception;
}
/**
* Mark the event as allowing a custom response code.
*/
public function allowCustomResponseCode()
{
$this->allowCustomResponseCode = true;
}
/**
* Returns true if the event allows a custom response code.
*
* @return bool
*/
public function isAllowingCustomResponseCode()
{
return $this->allowCustomResponseCode;
}
}

View File

@ -242,10 +242,12 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface
// the developer asked for a specific status code
if ($response->headers->has('X-Status-Code')) {
@trigger_error(sprintf('Using the X-Status-Code header is deprecated since version 3.3 and will be removed in 4.0. Use %s::allowCustomResponseCode() instead.', GetResponseForExceptionEvent::class), E_USER_DEPRECATED);
$response->setStatusCode($response->headers->get('X-Status-Code'));
$response->headers->remove('X-Status-Code');
} elseif (!$response->isClientError() && !$response->isServerError() && !$response->isRedirect()) {
} elseif (!$event->isAllowingCustomResponseCode() && !$response->isClientError() && !$response->isServerError() && !$response->isRedirect()) {
// ensure that we actually have an error response
if ($e instanceof HttpExceptionInterface) {
// keep the HTTP status code and headers

View File

@ -0,0 +1,27 @@
<?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\Event;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Tests\TestHttpKernel;
class GetResponseForExceptionEventTest extends TestCase
{
public function testAllowSuccessfulResponseIsFalseByDefault()
{
$event = new GetResponseForExceptionEvent(new TestHttpKernel(), new Request(), 1, new \Exception());
$this->assertFalse($event->isAllowingCustomResponseCode());
}
}

View File

@ -17,6 +17,7 @@ use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface;
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
@ -111,9 +112,10 @@ class HttpKernelTest extends TestCase
}
/**
* @group legacy
* @dataProvider getStatusCodes
*/
public function testHandleWhenAnExceptionIsHandledWithASpecificStatusCode($responseStatusCode, $expectedStatusCode)
public function testLegacyHandleWhenAnExceptionIsHandledWithASpecificStatusCode($responseStatusCode, $expectedStatusCode)
{
$dispatcher = new EventDispatcher();
$dispatcher->addListener(KernelEvents::EXCEPTION, function ($event) use ($responseStatusCode, $expectedStatusCode) {
@ -137,6 +139,32 @@ class HttpKernelTest extends TestCase
);
}
/**
* @dataProvider getSpecificStatusCodes
*/
public function testHandleWhenAnExceptionIsHandledWithASpecificStatusCode($expectedStatusCode)
{
$dispatcher = new EventDispatcher();
$dispatcher->addListener(KernelEvents::EXCEPTION, function (GetResponseForExceptionEvent $event) use ($expectedStatusCode) {
$event->allowCustomResponseCode();
$event->setResponse(new Response('', $expectedStatusCode));
});
$kernel = $this->getHttpKernel($dispatcher, function () { throw new \RuntimeException(); });
$response = $kernel->handle(new Request());
$this->assertEquals($expectedStatusCode, $response->getStatusCode());
}
public function getSpecificStatusCodes()
{
return array(
array(200),
array(302),
array(403),
);
}
public function testHandleWhenAListenerReturnsAResponse()
{
$dispatcher = new EventDispatcher();

View File

@ -54,7 +54,7 @@ class FormAuthenticationEntryPoint implements AuthenticationEntryPointInterface
$response = $this->httpKernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
if (200 === $response->getStatusCode()) {
$response->headers->set('X-Status-Code', 401);
$response->setStatusCode(401);
}
return $response;

View File

@ -112,6 +112,7 @@ class ExceptionListener
try {
$event->setResponse($this->startAuthentication($event->getRequest(), $exception));
$event->allowCustomResponseCode();
} catch (\Exception $e) {
$event->setException($e);
}
@ -155,6 +156,7 @@ class ExceptionListener
$subRequest->attributes->set(Security::ACCESS_DENIED_ERROR, $exception);
$event->setResponse($event->getKernel()->handle($subRequest, HttpKernelInterface::SUB_REQUEST, true));
$event->allowCustomResponseCode();
}
} catch (\Exception $e) {
if (null !== $this->logger) {

View File

@ -64,6 +64,6 @@ class FormAuthenticationEntryPointTest extends TestCase
$entryPointResponse = $entryPoint->start($request);
$this->assertEquals($response, $entryPointResponse);
$this->assertEquals(401, $entryPointResponse->headers->get('X-Status-Code'));
$this->assertEquals(401, $entryPointResponse->getStatusCode());
}
}

View File

@ -44,14 +44,19 @@ class ExceptionListenerTest extends TestCase
/**
* @dataProvider getAuthenticationExceptionProvider
*/
public function testAuthenticationExceptionWithEntryPoint(\Exception $exception, \Exception $eventException = null)
public function testAuthenticationExceptionWithEntryPoint(\Exception $exception)
{
$event = $this->createEvent($exception = new AuthenticationException());
$event = $this->createEvent($exception);
$listener = $this->createExceptionListener(null, null, null, $this->createEntryPoint());
$response = new Response('Forbidden', 403);
$listener = $this->createExceptionListener(null, null, null, $this->createEntryPoint($response));
$listener->onKernelException($event);
$this->assertEquals('OK', $event->getResponse()->getContent());
$this->assertTrue($event->isAllowingCustomResponseCode());
$this->assertEquals('Forbidden', $event->getResponse()->getContent());
$this->assertEquals(403, $event->getResponse()->getStatusCode());
$this->assertSame($exception, $event->getException());
}
@ -100,7 +105,7 @@ class ExceptionListenerTest extends TestCase
public function testAccessDeniedExceptionFullFledgedAndWithoutAccessDeniedHandlerAndWithErrorPage(\Exception $exception, \Exception $eventException = null)
{
$kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock();
$kernel->expects($this->once())->method('handle')->will($this->returnValue(new Response('error')));
$kernel->expects($this->once())->method('handle')->will($this->returnValue(new Response('Unauthorized', 401)));
$event = $this->createEvent($exception, $kernel);
@ -110,7 +115,10 @@ class ExceptionListenerTest extends TestCase
$listener = $this->createExceptionListener(null, $this->createTrustResolver(true), $httpUtils, null, '/error');
$listener->onKernelException($event);
$this->assertEquals('error', $event->getResponse()->getContent());
$this->assertTrue($event->isAllowingCustomResponseCode());
$this->assertEquals('Unauthorized', $event->getResponse()->getContent());
$this->assertEquals(401, $event->getResponse()->getStatusCode());
$this->assertSame(null === $eventException ? $exception : $eventException, $event->getException()->getPrevious());
}
@ -159,10 +167,10 @@ class ExceptionListenerTest extends TestCase
);
}
private function createEntryPoint()
private function createEntryPoint(Response $response = null)
{
$entryPoint = $this->getMockBuilder('Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface')->getMock();
$entryPoint->expects($this->once())->method('start')->will($this->returnValue(new Response('OK')));
$entryPoint->expects($this->once())->method('start')->will($this->returnValue($response ?: new Response('OK')));
return $entryPoint;
}

View File

@ -20,7 +20,7 @@
"symfony/security-core": "~3.2",
"symfony/event-dispatcher": "~2.8|~3.0",
"symfony/http-foundation": "~2.8|~3.0",
"symfony/http-kernel": "~2.8|~3.0",
"symfony/http-kernel": "~3.3",
"symfony/polyfill-php56": "~1.0",
"symfony/polyfill-php70": "~1.0",
"symfony/property-access": "~2.8|~3.0"

View File

@ -19,7 +19,7 @@
"php": ">=5.5.9",
"symfony/event-dispatcher": "~2.8|~3.0",
"symfony/http-foundation": "~2.8|~3.0",
"symfony/http-kernel": "~2.8|~3.0",
"symfony/http-kernel": "~3.3",
"symfony/polyfill-php56": "~1.0",
"symfony/polyfill-php70": "~1.0",
"symfony/polyfill-util": "~1.0",