From c3b4c341ddb12664dfe1dc9b82649dff9c83dceb Mon Sep 17 00:00:00 2001 From: leocavalcante Date: Sun, 11 Jul 2021 14:15:07 -0300 Subject: [PATCH] Adding PSR RequestHandlerInterface for Swoole --- README.md | 24 ++++++++ composer.json | 2 + src/RequestHandlerRunner.php | 81 +++++++++++++++++++++++++ src/Runtime.php | 5 ++ tests/E2E/StaticFileHandlerTest.php | 3 +- tests/Unit/RequestHandlerRunnerTest.php | 54 +++++++++++++++++ tests/Unit/RuntimeTest.php | 13 ++++ 7 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 src/RequestHandlerRunner.php create mode 100644 tests/Unit/RequestHandlerRunnerTest.php diff --git a/README.md b/README.md index 97c617e..3e09891 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,30 @@ return function () { }; ``` +### PSR + +```php +// public/index.php + +use Nyholm\Psr7\Response; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\RequestHandlerInterface; + +require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; + +class App implements RequestHandlerInterface { + public function handle(ServerRequestInterface $request): ResponseInterface { + $name = $request->getQueryParams()['name'] ?? 'World'; + return new Response(200, ['Server' => 'swoole-runtime'], "Hello, $name!"); + } +} + +return function(): RequestHandlerInterface { + return new App(); +}; +``` + ### Symfony ```php diff --git a/composer.json b/composer.json index 180b2f8..4dd20e3 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,8 @@ } ], "require": { + "nyholm/psr7": "^1.4", + "psr/http-server-handler": "^1.0", "symfony/runtime": "^5.3 || ^6.0" }, "require-dev": { diff --git a/src/RequestHandlerRunner.php b/src/RequestHandlerRunner.php new file mode 100644 index 0000000..f55bc08 --- /dev/null +++ b/src/RequestHandlerRunner.php @@ -0,0 +1,81 @@ +serverFactory = $serverFactory; + $this->application = $application; + } + + public function run(): int + { + $this->serverFactory->createServer([$this, 'handle'])->start(); + + return 0; + } + + public function handle(Request $request, Response $response): void + { + $psrRequest = (new \Nyholm\Psr7\ServerRequest( + $request->getMethod(), + $request->server['request_uri'] ?? '/', + array_change_key_case($request->server ?? [], CASE_UPPER), + $request->rawContent(), + '1.1', + $request->server ?? [] + )) + ->withQueryParams($request->get ?? []); + + $psrResponse = $this->application->handle($psrRequest); + + $response->setStatusCode($psrResponse->getStatusCode(), $psrResponse->getReasonPhrase()); + + foreach ($psrResponse->getHeaders() as $name => $values) { + foreach ($values as $value) { + $response->setHeader($name, $value); + } + } + + $body = $psrResponse->getBody(); + $body->rewind(); + + if ($body->isReadable()) { + if ($body->getSize() <= self::CHUNK_SIZE) { + if ($contents = $body->getContents()) { + $response->write($contents); + } + } else { + while (!$body->eof() && ($contents = $body->read(self::CHUNK_SIZE))) { + $response->write($contents); + } + } + + $response->end(); + } else { + $response->end((string) $body); + } + + $body->close(); + } +} diff --git a/src/Runtime.php b/src/Runtime.php index 38c9aff..24e9f88 100644 --- a/src/Runtime.php +++ b/src/Runtime.php @@ -3,6 +3,7 @@ namespace Runtime\Swoole; use Illuminate\Contracts\Http\Kernel; +use Psr\Http\Server\RequestHandlerInterface; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Runtime\RunnerInterface; use Symfony\Component\Runtime\SymfonyRuntime; @@ -37,6 +38,10 @@ public function getRunner(?object $application): RunnerInterface return new LaravelRunner($this->serverFactory, $application); } + if ($application instanceof RequestHandlerInterface) { + return new RequestHandlerRunner($this->serverFactory, $application); + } + return parent::getRunner($application); } } diff --git a/tests/E2E/StaticFileHandlerTest.php b/tests/E2E/StaticFileHandlerTest.php index 8a34fb0..7deb51c 100644 --- a/tests/E2E/StaticFileHandlerTest.php +++ b/tests/E2E/StaticFileHandlerTest.php @@ -4,12 +4,13 @@ use PHPUnit\Framework\TestCase; use function Swoole\Coroutine\Http\get; +use function Swoole\Coroutine\run; class StaticFileHandlerTest extends TestCase { public function testSwooleServerHandlesStaticFiles(): void { - \Co\run(static function (): void { + run(static function (): void { self::assertSame("Static file\n", get('http://localhost:8001/file.txt')->getBody()); }); } diff --git a/tests/Unit/RequestHandlerRunnerTest.php b/tests/Unit/RequestHandlerRunnerTest.php new file mode 100644 index 0000000..781d663 --- /dev/null +++ b/tests/Unit/RequestHandlerRunnerTest.php @@ -0,0 +1,54 @@ +createMock(Server::class); + $factory = $this->createMock(ServerFactory::class); + $application = $this->createMock(RequestHandlerInterface::class); + + $factory->expects(self::once())->method('createServer')->willReturn($server); + $server->expects(self::once())->method('start'); + + $runner = new RequestHandlerRunner($factory, $application); + + self::assertSame(0, $runner->run()); + } + + public function testHandle(): void + { + $factory = $this->createMock(ServerFactory::class); + $application = $this->createMock(RequestHandlerInterface::class); + $psrResponse = $this->createMock(ResponseInterface::class); + $request = $this->createMock(Request::class); + $response = $this->createMock(Response::class); + + $request->expects(self::once())->method('getMethod')->willReturn('POST'); + $request->expects(self::once())->method('rawContent')->willReturn('Test'); + + $application->expects(self::once())->method('handle')->willReturn($psrResponse); + + $psrResponse->expects(self::once())->method('getHeaders')->willReturn([ + 'X-Test' => ['Swoole-Runtime'], + ]); + $psrResponse->expects(self::once())->method('getBody')->willReturn(Stream::create('Test')); + + $response->expects(self::once())->method('setHeader')->with('X-Test', 'Swoole-Runtime'); + $response->expects(self::once())->method('write')->with('Test'); + $response->expects(self::once())->method('end')->with(null); + + $runner = new RequestHandlerRunner($factory, $application); + $runner->handle($request, $response); + } +} diff --git a/tests/Unit/RuntimeTest.php b/tests/Unit/RuntimeTest.php index b39754d..40c69b6 100644 --- a/tests/Unit/RuntimeTest.php +++ b/tests/Unit/RuntimeTest.php @@ -4,8 +4,10 @@ use Illuminate\Contracts\Http\Kernel; use PHPUnit\Framework\TestCase; +use Psr\Http\Server\RequestHandlerInterface; use Runtime\Swoole\CallableRunner; use Runtime\Swoole\LaravelRunner; +use Runtime\Swoole\RequestHandlerRunner; use Runtime\Swoole\Runtime; use Runtime\Swoole\SymfonyRunner; use Symfony\Component\HttpKernel\HttpKernelInterface; @@ -47,6 +49,17 @@ public function testGetRunnerCreatesARunnerForLaravel(): void self::assertInstanceOf(LaravelRunner::class, $runner); } + public function testGetRunnerCreatesARunnerForRequestHandlers(): void + { + $options = []; + $runtime = new Runtime($options); + + $application = $this->createMock(RequestHandlerInterface::class); + $runner = $runtime->getRunner($application); + + self::assertInstanceOf(RequestHandlerRunner::class, $runner); + } + public function testGetRunnerFallbacksToClosureRunner(): void { $options = [];