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): add support for StreamedResponse
Browse files Browse the repository at this point in the history
  • Loading branch information
supersmile2009 authored and k911 committed Feb 3, 2021
1 parent 807ba9f commit 89fc7ca
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

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\Reference;

/**
* Replaces Symfony's native StreamedResponseListener with a custom one compatible with Swoole.
*/
final class StreamedResponseListenerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
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)
;
}
$definition->setArgument(1, new Reference(StreamedResponseProcessor::class));
}
}
21 changes: 21 additions & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,33 @@ services:

K911\Swoole\Bridge\Symfony\HttpFoundation\SetRequestRuntimeConfiguration:

K911\Swoole\Bridge\Symfony\HttpFoundation\SwooleRequestResponseContextManager:

K911\Swoole\Bridge\Symfony\HttpFoundation\RequestFactoryInterface:
class: K911\Swoole\Bridge\Symfony\HttpFoundation\RequestFactory

K911\Swoole\Bridge\Symfony\HttpFoundation\ResponseProcessorInterface:
class: K911\Swoole\Bridge\Symfony\HttpFoundation\ResponseProcessor

K911\Swoole\Bridge\Symfony\HttpFoundation\NoOpStreamedResponseProcessor:
decorates: K911\Swoole\Bridge\Symfony\HttpFoundation\ResponseProcessorInterface
arguments:
- '@K911\Swoole\Bridge\Symfony\HttpFoundation\NoOpStreamedResponseProcessor.inner'

response_processor.headers_and_cookies.default:
class: K911\Swoole\Bridge\Symfony\HttpFoundation\ResponseHeadersAndStatusProcessor
decorates: K911\Swoole\Bridge\Symfony\HttpFoundation\ResponseProcessorInterface
arguments:
- '@response_processor.headers_and_cookies.default.inner'

K911\Swoole\Bridge\Symfony\HttpFoundation\StreamedResponseProcessor:

response_processor.headers_and_cookies.streamed:
class: K911\Swoole\Bridge\Symfony\HttpFoundation\ResponseHeadersAndStatusProcessor
decorates: K911\Swoole\Bridge\Symfony\HttpFoundation\StreamedResponseProcessor
arguments:
- '@response_processor.headers_and_cookies.streamed.inner'

K911\Swoole\Server\RequestHandler\RequestHandlerInterface:
alias: K911\Swoole\Server\RequestHandler\ExceptionRequestHandler

Expand Down
2 changes: 2 additions & 0 deletions src/Bridge/Symfony/Bundle/SwooleBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace K911\Swoole\Bridge\Symfony\Bundle;

use K911\Swoole\Bridge\Symfony\Bundle\DependencyInjection\CompilerPass\DebugLogProcessorPass;
use K911\Swoole\Bridge\Symfony\Bundle\DependencyInjection\CompilerPass\StreamedResponseListenerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

Expand All @@ -13,5 +14,6 @@ final class SwooleBundle extends Bundle
public function build(ContainerBuilder $container): void
{
$container->addCompilerPass(new DebugLogProcessorPass());
$container->addCompilerPass(new StreamedResponseListenerPass());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace K911\Swoole\Bridge\Symfony\HttpFoundation;

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

final class NoOpStreamedResponseProcessor implements ResponseProcessorInterface
{
/**
* @var ResponseProcessorInterface
*/
private $decorated;

public function __construct(ResponseProcessorInterface $decorated)
{
$this->decorated = $decorated;
}

/**
* {@inheritdoc}
*/
public function process(HttpFoundationResponse $httpFoundationResponse, SwooleResponse $swooleResponse): void
{
if ($httpFoundationResponse instanceof StreamedResponse) {
return;
}

$this->decorated->process($httpFoundationResponse, $swooleResponse);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace K911\Swoole\Bridge\Symfony\HttpFoundation;

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

final class ResponseHeadersAndStatusProcessor implements ResponseProcessorInterface
{
/**
* @var ResponseProcessorInterface
*/
private $decorated;

public function __construct(ResponseProcessorInterface $decorated)
{
$this->decorated = $decorated;
}

/**
* {@inheritdoc}
*/
public function process(HttpFoundationResponse $httpFoundationResponse, SwooleResponse $swooleResponse): void
{
foreach ($httpFoundationResponse->headers->allPreserveCaseWithoutCookies() as $name => $values) {
$swooleResponse->header($name, \implode(', ', $values));
}

foreach ($httpFoundationResponse->headers->getCookies() as $cookie) {
$swooleResponse->cookie(
$cookie->getName(),
$cookie->getValue() ?? '',
$cookie->getExpiresTime(),
$cookie->getPath(),
$cookie->getDomain() ?? '',
$cookie->isSecure(),
$cookie->isHttpOnly(),
$cookie->getSameSite() ?? ''
);
}

$swooleResponse->status($httpFoundationResponse->getStatusCode());

$this->decorated->process($httpFoundationResponse, $swooleResponse);
}
}
25 changes: 0 additions & 25 deletions src/Bridge/Symfony/HttpFoundation/ResponseProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@

namespace K911\Swoole\Bridge\Symfony\HttpFoundation;

use RuntimeException;
use Swoole\Http\Response as SwooleResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Response as HttpFoundationResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;

final class ResponseProcessor implements ResponseProcessorInterface
{
Expand All @@ -17,29 +15,6 @@ final class ResponseProcessor implements ResponseProcessorInterface
*/
public function process(HttpFoundationResponse $httpFoundationResponse, SwooleResponse $swooleResponse): void
{
if ($httpFoundationResponse instanceof StreamedResponse) {
throw new RuntimeException('HttpFoundation "StreamedResponse" response object is not yet supported');
}

foreach ($httpFoundationResponse->headers->allPreserveCaseWithoutCookies() as $name => $values) {
$swooleResponse->header($name, \implode(', ', $values));
}

foreach ($httpFoundationResponse->headers->getCookies() as $cookie) {
$swooleResponse->cookie(
$cookie->getName(),
$cookie->getValue() ?? '',
$cookie->getExpiresTime(),
$cookie->getPath(),
$cookie->getDomain() ?? '',
$cookie->isSecure(),
$cookie->isHttpOnly(),
$cookie->getSameSite() ?? ''
);
}

$swooleResponse->status($httpFoundationResponse->getStatusCode());

if ($httpFoundationResponse instanceof BinaryFileResponse) {
$swooleResponse->sendfile($httpFoundationResponse->getFile()->getRealPath());
} else {
Expand Down
44 changes: 44 additions & 0 deletions src/Bridge/Symfony/HttpFoundation/StreamedResponseListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace K911\Swoole\Bridge\Symfony\HttpFoundation;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
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;
}

public function onKernelResponse(ResponseEvent $event): void
{
if (!$event->isMasterRequest()) {
return;
}

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

public static function getSubscribedEvents()
{
return [
KernelEvents::RESPONSE => ['onKernelResponse', -1024],
];
}
}
39 changes: 39 additions & 0 deletions src/Bridge/Symfony/HttpFoundation/StreamedResponseProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace K911\Swoole\Bridge\Symfony\HttpFoundation;

use Assert\Assertion;
use Swoole\Http\Response as SwooleResponse;
use Symfony\Component\HttpFoundation\Response as HttpFoundationResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;

final class StreamedResponseProcessor implements ResponseProcessorInterface
{
private $bufferOutputSize;

public function __construct(int $bufferOutputSize = 8192)
{
$this->bufferOutputSize = $bufferOutputSize;
}

/**
* {@inheritdoc}
*/
public function process(HttpFoundationResponse $httpFoundationResponse, SwooleResponse $swooleResponse): void
{
Assertion::isInstanceOf($httpFoundationResponse, StreamedResponse::class);

\ob_start(static function (string $payload) use ($swooleResponse) {
if ('' !== $payload) {
$swooleResponse->write($payload);
}

return '';
}, $this->bufferOutputSize);
$httpFoundationResponse->sendContent();
\ob_end_clean();
$swooleResponse->end();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace K911\Swoole\Bridge\Symfony\HttpFoundation;

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

final class SwooleRequestResponseContextManager
{
private const REQUEST_ATTR_KEY = 'swoole_request';
private const RESPONSE_ATTR_KEY = 'swoole_response';

public function attachRequestResponseAttributes(
HttpFoundationRequest $request,
SwooleRequest $swooleRequest,
SwooleResponse $swooleResponse
): void {
$request->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);
}
}
12 changes: 10 additions & 2 deletions src/Bridge/Symfony/HttpKernel/HttpKernelRequestHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use K911\Swoole\Bridge\Symfony\HttpFoundation\RequestFactoryInterface;
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 @@ -15,15 +16,21 @@

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

public function __construct(KernelInterface $kernel, RequestFactoryInterface $requestFactory, ResponseProcessorInterface $responseProcessor)
{
public function __construct(
KernelInterface $kernel,
RequestFactoryInterface $requestFactory,
SwooleRequestResponseContextManager $contextManager,
ResponseProcessorInterface $responseProcessor
) {
$this->kernel = $kernel;
$this->requestFactory = $requestFactory;
$this->responseProcessor = $responseProcessor;
$this->contextManager = $contextManager;
}

/**
Expand All @@ -42,6 +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);
$httpFoundationResponse = $this->kernel->handle($httpFoundationRequest);
$this->responseProcessor->process($httpFoundationResponse, $response);

Expand Down

0 comments on commit 89fc7ca

Please sign in to comment.