From c132552e03ad71d3f4478c818740ff7d7fbefe5d Mon Sep 17 00:00:00 2001 From: k911 Date: Fri, 30 Apr 2021 15:55:00 +0200 Subject: [PATCH] feat(response-processor): hiding StreamedResponse support from Symfony --- .../StreamedResponseListenerPass.php | 30 +++++++++------- .../Bundle/Resources/config/services.yaml | 7 +++- .../ResponseProcessorInjector.php | 31 ++++++++++++++++ .../ResponseProcessorInjectorInterface.php | 15 ++++++++ .../StreamedResponseListener.php | 36 ++++++++++++------- .../SwooleRequestResponseContextManager.php | 36 ------------------- .../HttpKernel/HttpKernelRequestHandler.php | 10 +++--- ...mfonyHttpRequestContainsRequestUriTest.php | 31 ++++++++++++++++ .../Controller/SymfonyHttpController.php | 18 ++++++++++ .../HttpKernel/HttpKernelHttpDriverTest.php | 22 ++++++++++-- 10 files changed, 167 insertions(+), 69 deletions(-) create mode 100644 src/Bridge/Symfony/HttpFoundation/ResponseProcessorInjector.php create mode 100644 src/Bridge/Symfony/HttpFoundation/ResponseProcessorInjectorInterface.php delete mode 100644 src/Bridge/Symfony/HttpFoundation/SwooleRequestResponseContextManager.php diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/CompilerPass/StreamedResponseListenerPass.php b/src/Bridge/Symfony/Bundle/DependencyInjection/CompilerPass/StreamedResponseListenerPass.php index a4127bf7..d0ca797e 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/CompilerPass/StreamedResponseListenerPass.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/CompilerPass/StreamedResponseListenerPass.php @@ -5,9 +5,9 @@ namespace K911\Swoole\Bridge\Symfony\Bundle\DependencyInjection\CompilerPass; use K911\Swoole\Bridge\Symfony\HttpFoundation\StreamedResponseListener; -use K911\Swoole\Bridge\Symfony\HttpFoundation\StreamedResponseProcessor; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; /** @@ -17,18 +17,24 @@ final class StreamedResponseListenerPass implements CompilerPassInterface { public function process(ContainerBuilder $container): void { + $oldListenerDefinition = null; + $definitionId = 'streamed_response_listener'; + $oldDefinitionId = \sprintf('%s.original', $definitionId); + if ($container->hasDefinition('streamed_response_listener')) { - $definition = $container->getDefinition('streamed_response_listener'); - $definition - ->setClass(StreamedResponseListener::class) - ->setAutowired(true) - ; - } else { - $definition = $container - ->autowire('streamed_response_listener', StreamedResponseListener::class) - ->setAutoconfigured(true) - ; + $oldListenerDefinition = $container->getDefinition('streamed_response_listener'); + $oldListenerDefinition->clearTag('kernel.event_subscriber'); + $container->setDefinition($oldDefinitionId, $oldListenerDefinition); } - $definition->setArgument(1, new Reference(StreamedResponseProcessor::class)); + + $newDefinition = new Definition(StreamedResponseListener::class); + $newDefinition->setAutoconfigured(true); + $newDefinition->addTag('kernel.event_subscriber'); + + if (null !== $oldListenerDefinition) { + $newDefinition->setArgument('$delegate', new Reference($oldDefinitionId)); + } + + $container->setDefinition($definitionId, $newDefinition); } } diff --git a/src/Bridge/Symfony/Bundle/Resources/config/services.yaml b/src/Bridge/Symfony/Bundle/Resources/config/services.yaml index a100f123..24490f5d 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/services.yaml +++ b/src/Bridge/Symfony/Bundle/Resources/config/services.yaml @@ -19,7 +19,11 @@ services: K911\Swoole\Bridge\Symfony\HttpFoundation\SetRequestRuntimeConfiguration: - K911\Swoole\Bridge\Symfony\HttpFoundation\SwooleRequestResponseContextManager: + K911\Swoole\Bridge\Symfony\HttpFoundation\ResponseProcessorInjectorInterface: '@K911\Swoole\Bridge\Symfony\HttpFoundation\ResponseProcessorInjector' + + K911\Swoole\Bridge\Symfony\HttpFoundation\ResponseProcessorInjector: + arguments: + $responseProcessor: '@response_processor.headers_and_cookies.streamed' K911\Swoole\Bridge\Symfony\HttpFoundation\RequestFactoryInterface: class: K911\Swoole\Bridge\Symfony\HttpFoundation\RequestFactory @@ -29,6 +33,7 @@ services: K911\Swoole\Bridge\Symfony\HttpFoundation\NoOpStreamedResponseProcessor: decorates: K911\Swoole\Bridge\Symfony\HttpFoundation\ResponseProcessorInterface + decoration_priority: -100 arguments: - '@K911\Swoole\Bridge\Symfony\HttpFoundation\NoOpStreamedResponseProcessor.inner' diff --git a/src/Bridge/Symfony/HttpFoundation/ResponseProcessorInjector.php b/src/Bridge/Symfony/HttpFoundation/ResponseProcessorInjector.php new file mode 100644 index 00000000..52536ba2 --- /dev/null +++ b/src/Bridge/Symfony/HttpFoundation/ResponseProcessorInjector.php @@ -0,0 +1,31 @@ +responseProcessor = $responseProcessor; + } + + public function injectProcessor( + HttpFoundationRequest $request, + SwooleResponse $swooleResponse + ): void { + $request->attributes->set( + self::ATTR_KEY_RESPONSE_PROCESSOR, + function (HttpFoundationResponse $httpFoundationResponse) use ($swooleResponse): void { + $this->responseProcessor->process($httpFoundationResponse, $swooleResponse); + } + ); + } +} diff --git a/src/Bridge/Symfony/HttpFoundation/ResponseProcessorInjectorInterface.php b/src/Bridge/Symfony/HttpFoundation/ResponseProcessorInjectorInterface.php new file mode 100644 index 00000000..03201008 --- /dev/null +++ b/src/Bridge/Symfony/HttpFoundation/ResponseProcessorInjectorInterface.php @@ -0,0 +1,15 @@ +responseProcessor = $responseProcessor; - $this->contextManager = $contextManager; + private $delegate; + + public function __construct(?HttpFoundationStreamedResponseListener $delegate = null) + { + $this->delegate = $delegate; } public function onKernelResponse(ResponseEvent $event): void @@ -29,10 +26,25 @@ public function onKernelResponse(ResponseEvent $event): void } $response = $event->getResponse(); - if ($response instanceof StreamedResponse) { - $swooleResponse = $this->contextManager->findResponse($event->getRequest()); - $this->responseProcessor->process($response, $swooleResponse); + + if (!$response instanceof StreamedResponse) { + return; } + + $attributes = $event->getRequest()->attributes; + + if ($attributes->has(ResponseProcessorInjectorInterface::ATTR_KEY_RESPONSE_PROCESSOR)) { + $processor = $attributes->get(ResponseProcessorInjectorInterface::ATTR_KEY_RESPONSE_PROCESSOR); + $processor($response); + + return; + } + + if (null === $this->delegate) { + return; + } + + $this->delegate->onKernelResponse($event); } public static function getSubscribedEvents() diff --git a/src/Bridge/Symfony/HttpFoundation/SwooleRequestResponseContextManager.php b/src/Bridge/Symfony/HttpFoundation/SwooleRequestResponseContextManager.php deleted file mode 100644 index 23e774d7..00000000 --- a/src/Bridge/Symfony/HttpFoundation/SwooleRequestResponseContextManager.php +++ /dev/null @@ -1,36 +0,0 @@ -attributes->set(static::REQUEST_ATTR_KEY, $swooleRequest); - $request->attributes->set(static::RESPONSE_ATTR_KEY, $swooleResponse); - } - - public function findRequest( - HttpFoundationRequest $request - ): SwooleRequest { - return $request->attributes->get(static::REQUEST_ATTR_KEY); - } - - public function findResponse( - HttpFoundationRequest $request - ): SwooleResponse { - return $request->attributes->get(static::RESPONSE_ATTR_KEY); - } -} diff --git a/src/Bridge/Symfony/HttpKernel/HttpKernelRequestHandler.php b/src/Bridge/Symfony/HttpKernel/HttpKernelRequestHandler.php index b6a4285f..ceebf176 100644 --- a/src/Bridge/Symfony/HttpKernel/HttpKernelRequestHandler.php +++ b/src/Bridge/Symfony/HttpKernel/HttpKernelRequestHandler.php @@ -5,8 +5,8 @@ namespace K911\Swoole\Bridge\Symfony\HttpKernel; use K911\Swoole\Bridge\Symfony\HttpFoundation\RequestFactoryInterface; +use K911\Swoole\Bridge\Symfony\HttpFoundation\ResponseProcessorInjectorInterface; use K911\Swoole\Bridge\Symfony\HttpFoundation\ResponseProcessorInterface; -use K911\Swoole\Bridge\Symfony\HttpFoundation\SwooleRequestResponseContextManager; use K911\Swoole\Server\RequestHandler\RequestHandlerInterface; use K911\Swoole\Server\Runtime\BootableInterface; use Swoole\Http\Request as SwooleRequest; @@ -16,7 +16,7 @@ final class HttpKernelRequestHandler implements RequestHandlerInterface, BootableInterface { - private $contextManager; + private $processorInjector; private $kernel; private $requestFactory; private $responseProcessor; @@ -24,13 +24,13 @@ final class HttpKernelRequestHandler implements RequestHandlerInterface, Bootabl public function __construct( KernelInterface $kernel, RequestFactoryInterface $requestFactory, - SwooleRequestResponseContextManager $contextManager, + ResponseProcessorInjectorInterface $processorInjector, ResponseProcessorInterface $responseProcessor ) { $this->kernel = $kernel; $this->requestFactory = $requestFactory; $this->responseProcessor = $responseProcessor; - $this->contextManager = $contextManager; + $this->processorInjector = $processorInjector; } /** @@ -49,7 +49,7 @@ public function boot(array $runtimeConfiguration = []): void public function handle(SwooleRequest $request, SwooleResponse $response): void { $httpFoundationRequest = $this->requestFactory->make($request); - $this->contextManager->attachRequestResponseAttributes($httpFoundationRequest, $request, $response); + $this->processorInjector->injectProcessor($httpFoundationRequest, $response); $httpFoundationResponse = $this->kernel->handle($httpFoundationRequest); $this->responseProcessor->process($httpFoundationResponse, $response); diff --git a/tests/Feature/SymfonyHttpRequestContainsRequestUriTest.php b/tests/Feature/SymfonyHttpRequestContainsRequestUriTest.php index a0322224..85d30e6c 100644 --- a/tests/Feature/SymfonyHttpRequestContainsRequestUriTest.php +++ b/tests/Feature/SymfonyHttpRequestContainsRequestUriTest.php @@ -44,4 +44,35 @@ public function testWhetherCurrentSymfonyHttpRequestContainsRequestUri(): void $serverRun->stop(); } + + /* + * Test whether current Symfony's Request->getRequestUri() is working + * @see https://github.com/k911/swoole-bundle/issues/268 + */ + public function testWhetherCurrentSymfonyHttpRequestContainsRequestUriInStreamedResponse(): void + { + $serverRun = $this->createConsoleProcess([ + 'swoole:server:run', + '--host=localhost', + '--port=9999', + ]); + + $serverRun->setTimeout(10); + $serverRun->start(); + + $this->runAsCoroutineAndWait(function (): void { + $client = HttpClient::fromDomain('localhost', 9999, false); + $this->assertTrue($client->connect()); + + $uri = '/http/request/streamed-uri?test1=1&test2=test3'; + $response = $client->send('/http/request/streamed-uri?test1=1&test2=test3')['response']; + + $this->assertSame(200, $response['statusCode']); + $this->assertSame([ + 'requestUri' => $uri, + ], $response['body']); + }); + + $serverRun->stop(); + } } diff --git a/tests/Fixtures/Symfony/TestBundle/Controller/SymfonyHttpController.php b/tests/Fixtures/Symfony/TestBundle/Controller/SymfonyHttpController.php index 15553bfb..b1e8694a 100644 --- a/tests/Fixtures/Symfony/TestBundle/Controller/SymfonyHttpController.php +++ b/tests/Fixtures/Symfony/TestBundle/Controller/SymfonyHttpController.php @@ -6,6 +6,7 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\Routing\Annotation\Route; final class SymfonyHttpController @@ -22,4 +23,21 @@ public function getRequestUri(Request $currentRequest): JsonResponse { return new JsonResponse(['requestUri' => $currentRequest->getRequestUri()], 200); } + + /** + * @Route( + * methods={"GET"}, + * path="/http/request/streamed-uri" + * ) + */ + public function getStreamedRequestUri(Request $currentRequest): StreamedResponse + { + $response = new StreamedResponse(function () use ($currentRequest): void { + $response = ['requestUri' => $currentRequest->getRequestUri()]; + echo \json_encode($response); + }); + $response->headers->set('Content-Type', 'application/json'); + + return $response; + } } diff --git a/tests/Unit/Bridge/Symfony/HttpKernel/HttpKernelHttpDriverTest.php b/tests/Unit/Bridge/Symfony/HttpKernel/HttpKernelHttpDriverTest.php index 756aa91b..c7b1d94a 100644 --- a/tests/Unit/Bridge/Symfony/HttpKernel/HttpKernelHttpDriverTest.php +++ b/tests/Unit/Bridge/Symfony/HttpKernel/HttpKernelHttpDriverTest.php @@ -5,8 +5,8 @@ namespace K911\Swoole\Tests\Unit\Bridge\Symfony\HttpKernel; use K911\Swoole\Bridge\Symfony\HttpFoundation\RequestFactoryInterface; +use K911\Swoole\Bridge\Symfony\HttpFoundation\ResponseProcessorInjectorInterface; use K911\Swoole\Bridge\Symfony\HttpFoundation\ResponseProcessorInterface; -use K911\Swoole\Bridge\Symfony\HttpFoundation\SwooleRequestResponseContextManager; use K911\Swoole\Bridge\Symfony\HttpKernel\HttpKernelRequestHandler; use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\ObjectProphecy; @@ -35,6 +35,11 @@ class HttpKernelHttpDriverTest extends TestCase */ private $requestFactoryProphecy; + /** + * @var ObjectProphecy|ResponseProcessorInjectorInterface + */ + private $responseProcessorInjectorProphecy; + /** * @var KernelInterface|ObjectProphecy|TerminableInterface */ @@ -44,19 +49,22 @@ protected function setUp(): void { $this->kernelProphecy = $this->prophesize(KernelInterface::class); $this->requestFactoryProphecy = $this->prophesize(RequestFactoryInterface::class); + $this->responseProcessorInjectorProphecy = $this->prophesize(ResponseProcessorInjectorInterface::class); $this->responseProcessor = $this->prophesize(ResponseProcessorInterface::class); /** @var KernelInterface $kernelMock */ $kernelMock = $this->kernelProphecy->reveal(); /** @var RequestFactoryInterface $requestFactoryMock */ $requestFactoryMock = $this->requestFactoryProphecy->reveal(); + /** @var ResponseProcessorInjectorInterface $responseProcessorInjectorMock */ + $responseProcessorInjectorMock = $this->responseProcessorInjectorProphecy->reveal(); /** @var ResponseProcessorInterface $responseProcessorMock */ $responseProcessorMock = $this->responseProcessor->reveal(); $this->httpDriver = new HttpKernelRequestHandler( $kernelMock, $requestFactoryMock, - new SwooleRequestResponseContextManager(), + $responseProcessorInjectorMock, $responseProcessorMock ); } @@ -81,6 +89,9 @@ public function testHandleNonTerminable(): void $this->requestFactoryProphecy->make($swooleRequest)->willReturn($httpFoundationRequest)->shouldBeCalled(); $this->kernelProphecy->handle($httpFoundationRequest)->willReturn($httpFoundationResponse)->shouldBeCalled(); + $this->responseProcessorInjectorProphecy->injectProcessor($httpFoundationRequest, $swooleResponse) + ->shouldBeCalled() + ; $this->responseProcessor->process($httpFoundationResponse, $swooleResponse)->shouldBeCalled(); $this->httpDriver->handle($swooleRequest, $swooleResponse); @@ -101,6 +112,9 @@ public function testHandleTerminable(): void $this->requestFactoryProphecy->make($swooleRequest)->willReturn($httpFoundationRequest)->shouldBeCalled(); $this->kernelProphecy->handle($httpFoundationRequest)->willReturn($httpFoundationResponse)->shouldBeCalled(); + $this->responseProcessorInjectorProphecy->injectProcessor($httpFoundationRequest, $swooleResponse) + ->shouldBeCalled() + ; $this->responseProcessor->process($httpFoundationResponse, $swooleResponse)->shouldBeCalled(); $this->kernelProphecy->terminate($httpFoundationRequest, $httpFoundationResponse)->shouldBeCalled(); @@ -115,13 +129,15 @@ private function setUpTerminableKernel(): void $kernelMock = $this->kernelProphecy->reveal(); /** @var RequestFactoryInterface $requestFactoryMock */ $requestFactoryMock = $this->requestFactoryProphecy->reveal(); + /** @var ResponseProcessorInjectorInterface $responseProcessorInjectorMock */ + $responseProcessorInjectorMock = $this->responseProcessorInjectorProphecy->reveal(); /** @var ResponseProcessorInterface $responseProcessorMock */ $responseProcessorMock = $this->responseProcessor->reveal(); $this->httpDriver = new HttpKernelRequestHandler( $kernelMock, $requestFactoryMock, - new SwooleRequestResponseContextManager(), + $responseProcessorInjectorMock, $responseProcessorMock ); }