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

Add support for StreamedResponse #300

Merged
merged 3 commits into from
Feb 3, 2021
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
@@ -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
Loading