From 63a8b3e0fb5911e74575acb5a5b45fa55dd976cc Mon Sep 17 00:00:00 2001 From: Ismail Turan Date: Fri, 28 Jul 2023 08:26:11 +0200 Subject: [PATCH] [HttpKernel] Fix missing Request in RequestStack for StreamedResponse --- .../HttpFoundation/StreamedResponse.php | 8 +++++ .../Component/HttpKernel/HttpKernel.php | 27 ++++++++++++-- .../HttpKernel/Tests/HttpKernelTest.php | 36 +++++++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/StreamedResponse.php b/src/Symfony/Component/HttpFoundation/StreamedResponse.php index 2c8ff15f3650e..ef98d32a2c049 100644 --- a/src/Symfony/Component/HttpFoundation/StreamedResponse.php +++ b/src/Symfony/Component/HttpFoundation/StreamedResponse.php @@ -56,6 +56,14 @@ public function setCallback(callable $callback): static return $this; } + /** + * Decorates the PHP callback associated with this Response. + */ + public function getCallback(): callable + { + return $this->callback; + } + /** * This method only sends the headers once. * diff --git a/src/Symfony/Component/HttpKernel/HttpKernel.php b/src/Symfony/Component/HttpKernel/HttpKernel.php index 794a55dc18f9c..1b1916a677c8c 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernel.php +++ b/src/Symfony/Component/HttpKernel/HttpKernel.php @@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; @@ -70,8 +71,12 @@ public function handle(Request $request, int $type = HttpKernelInterface::MAIN_R $request->headers->set('X-Php-Ob-Level', (string) ob_get_level()); $this->requestStack->push($request); + $response = null; try { - return $this->handleRaw($request, $type); + $response = $this->handleRaw($request, $type); + $response = $this->decorateWhenStreamedResponse($response); + + return $response; } catch (\Throwable $e) { if ($e instanceof \Error && !$this->handleAllThrowables) { throw $e; @@ -88,7 +93,9 @@ public function handle(Request $request, int $type = HttpKernelInterface::MAIN_R return $this->handleThrowable($e, $request, $type); } finally { - $this->requestStack->pop(); + if (!$response instanceof StreamedResponse) { + $this->requestStack->pop(); + } } } @@ -257,6 +264,22 @@ private function handleThrowable(\Throwable $e, Request $request, int $type): Re } } + private function decorateWhenStreamedResponse(Response $response): Response + { + if ($response instanceof StreamedResponse) { + $streamedResponseCallback = $response->getCallback(); + $response->setCallback(function () use ($streamedResponseCallback) { + try { + $streamedResponseCallback(); + } finally { + $this->requestStack->pop(); + } + }); + } + + return $response; + } + /** * Returns a human-readable string for the specified variable. */ diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php index a5a240a6265ec..7148d4afe85f0 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php @@ -18,6 +18,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; use Symfony\Component\HttpKernel\Event\ExceptionEvent; @@ -457,6 +458,34 @@ public function testVerifyRequestStackPushPopDuringHandle() $kernel->handle($request, HttpKernelInterface::MAIN_REQUEST); } + public function testVerifyRequestStackPushPopCallOrderDuringHandle() + { + $request = new Request(); + $stack = new RequestStack(); + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, null, $stack); + + $response = $kernel->handle($request, HttpKernelInterface::MAIN_REQUEST); + self::assertNull($stack->getMainRequest()); + $response->send(); + self::assertNull($stack->getMainRequest()); + } + + public function testVerifyRequestStackPushPopWithStreamedResponse() + { + $request = new Request(); + $stack = new RequestStack(); + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, [new TestController(), 'streamedResponseController'], $stack); + + $response = $kernel->handle($request, HttpKernelInterface::MAIN_REQUEST); + self::assertNotNull($stack->getMainRequest()); + ob_start(); + $response->send(); + self::assertSame('foo', ob_get_clean()); + self::assertNull($stack->getMainRequest()); + } + public function testInconsistentClientIpsOnMainRequests() { $this->expectException(BadRequestHttpException::class); @@ -515,6 +544,13 @@ public function controller() return new Response('foo'); } + public function streamedResponseController() + { + return new StreamedResponse(function () { + echo 'foo'; + }); + } + public static function staticController() { return new Response('foo');