Skip to content
This repository has been archived by the owner on Jan 17, 2022. It is now read-only.

feat(response-processor): hiding Swoole StreamedResponse support from Symfony #509

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -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);
}
}
7 changes: 6 additions & 1 deletion src/Bridge/Symfony/Bundle/Resources/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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'

Expand Down
31 changes: 31 additions & 0 deletions src/Bridge/Symfony/HttpFoundation/ResponseProcessorInjector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace K911\Swoole\Bridge\Symfony\HttpFoundation;

use Swoole\Http\Response as SwooleResponse;
use Symfony\Component\HttpFoundation\Request as HttpFoundationRequest;
use Symfony\Component\HttpFoundation\Response as HttpFoundationResponse;

final class ResponseProcessorInjector implements ResponseProcessorInjectorInterface
{
private $responseProcessor;

public function __construct(ResponseProcessorInterface $responseProcessor)
{
$this->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);
}
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace K911\Swoole\Bridge\Symfony\HttpFoundation;

use Swoole\Http\Response as SwooleResponse;
use Symfony\Component\HttpFoundation\Request as HttpFoundationRequest;

interface ResponseProcessorInjectorInterface
{
public const ATTR_KEY_RESPONSE_PROCESSOR = 'swoole_streamed_response_processor';

public function injectProcessor(HttpFoundationRequest $request, SwooleResponse $swooleResponse): void;
}
33 changes: 21 additions & 12 deletions src/Bridge/Symfony/HttpFoundation/StreamedResponseListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,16 @@
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\EventListener\StreamedResponseListener as HttpFoundationStreamedResponseListener;
use Symfony\Component\HttpKernel\KernelEvents;

class StreamedResponseListener implements EventSubscriberInterface
{
private $contextManager;
private $responseProcessor;

public function __construct(
SwooleRequestResponseContextManager $contextManager,
ResponseProcessorInterface $responseProcessor
) {
$this->responseProcessor = $responseProcessor;
$this->contextManager = $contextManager;
private $delegate;

public function __construct(?HttpFoundationStreamedResponseListener $delegate = null)
{
$this->delegate = $delegate;
}

public function onKernelResponse(ResponseEvent $event): void
Expand All @@ -29,10 +26,22 @@ public function onKernelResponse(ResponseEvent $event): void
}

$response = $event->getResponse();
if ($response instanceof StreamedResponse) {
$swooleResponse = $this->contextManager->findResponse($event->getRequest());
$this->responseProcessor->process($response, $swooleResponse);
$attributes = $event->getRequest()->attributes;

if ($response instanceof StreamedResponse &&
$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()
Expand Down

This file was deleted.

10 changes: 5 additions & 5 deletions src/Bridge/Symfony/HttpKernel/HttpKernelRequestHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -16,21 +16,21 @@

final class HttpKernelRequestHandler implements RequestHandlerInterface, BootableInterface
{
private $contextManager;
private $processorInjector;
private $kernel;
private $requestFactory;
private $responseProcessor;

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;
}

/**
Expand All @@ -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);

Expand Down
31 changes: 31 additions & 0 deletions tests/Feature/SymfonyHttpRequestContainsRequestUriTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -35,6 +35,11 @@ class HttpKernelHttpDriverTest extends TestCase
*/
private $requestFactoryProphecy;

/**
* @var ObjectProphecy|ResponseProcessorInjectorInterface
*/
private $responseProcessorInjectorProphecy;

/**
* @var KernelInterface|ObjectProphecy|TerminableInterface
*/
Expand All @@ -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
);
}
Expand All @@ -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);
Expand All @@ -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();

Expand All @@ -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
);
}
Expand Down