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

Commit

Permalink
feat(response-processor): hiding Swoole StreamedResponse support from…
Browse files Browse the repository at this point in the history
… Symfony (#509)

Co-authored-by: k911 <konradobal@gmail.com>
  • Loading branch information
Martin Fris and k911 committed May 5, 2021
1 parent 8793695 commit ebe8077
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 69 deletions.
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;
}
}
22 changes: 19 additions & 3 deletions tests/Unit/Bridge/Symfony/HttpKernel/HttpKernelHttpDriverTest.php
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

0 comments on commit ebe8077

Please sign in to comment.