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 committed Jul 30, 2020
1 parent c90e03b commit 3123f7e
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace K911\Swoole\Bridge\Symfony\Bundle\DependencyInjection\CompilerPass;

use K911\Swoole\Bridge\Symfony\HttpFoundation\StreamedResponseListener;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
* 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 {
$container
->autowire('streamed_response_listener', StreamedResponseListener::class)
->setAutoconfigured(true)
;
}
}
}
2 changes: 2 additions & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ 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

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());
}
}
15 changes: 10 additions & 5 deletions src/Bridge/Symfony/HttpFoundation/ResponseProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

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;
Expand All @@ -17,10 +16,6 @@ final class ResponseProcessor implements ResponseProcessorInterface
*/
public function process(HttpFoundationResponse $httpFoundationResponse, SwooleResponse $swooleResponse): void
{
if ($httpFoundationResponse instanceof StreamedResponse) {
throw new RuntimeException(\sprintf('HttpFoundation "StreamedResponse" response object is not yet supported'));
}

foreach ($httpFoundationResponse->headers->allPreserveCaseWithoutCookies() as $name => $values) {
$swooleResponse->header($name, \implode(', ', $values));
}
Expand All @@ -42,6 +37,16 @@ public function process(HttpFoundationResponse $httpFoundationResponse, SwooleRe

if ($httpFoundationResponse instanceof BinaryFileResponse) {
$swooleResponse->sendfile($httpFoundationResponse->getFile()->getRealPath());
} elseif ($httpFoundationResponse instanceof StreamedResponse) {
ob_start(function (string $payload) use ($swooleResponse) {
if ($payload !== '') {
$swooleResponse->write($payload);
}
return '';
}, 8192);
$httpFoundationResponse->sendContent();
ob_end_clean();
$swooleResponse->end();
} else {
$swooleResponse->end($httpFoundationResponse->getContent());
}
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],
];
}
}
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);
}
}
18 changes: 15 additions & 3 deletions src/Bridge/Symfony/HttpKernel/HttpKernelRequestHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,33 @@

use K911\Swoole\Bridge\Symfony\HttpFoundation\RequestFactoryInterface;
use K911\Swoole\Bridge\Symfony\HttpFoundation\ResponseProcessorInterface;
use K911\Swoole\Bridge\Symfony\HttpFoundation\StreamedResponseListener;
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;
use Swoole\Http\Response as SwooleResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpKernel\TerminableInterface;

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,8 +51,11 @@ 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);
if (!$httpFoundationResponse instanceof StreamedResponse) {
$this->responseProcessor->process($httpFoundationResponse, $response);
}

if ($this->kernel instanceof TerminableInterface) {
$this->kernel->terminate($httpFoundationRequest, $httpFoundationResponse);
Expand Down

0 comments on commit 3123f7e

Please sign in to comment.