From 35b99361da9a46e5ddf1116e6ad854091cac5a82 Mon Sep 17 00:00:00 2001 From: Koldo Picaza Date: Sun, 17 Jan 2021 13:21:36 +0100 Subject: [PATCH] create psr-11 factories --- src/Container/Config/ConfigProvider.php | 12 +++- src/LoopFactory.php | 17 +++++ src/ReactApplication.php | 45 ++++++++++++- src/ReactApplicationFactory.php | 6 +- src/ServerFactory.php | 42 ++++++++++++ src/SocketFactory.php | 28 ++++++++ test/Container/Config/ConfigProviderTest.php | 20 +++++- test/LoopFactoryTest.php | 20 ++++++ test/ReactApplicationFactoryTest.php | 7 +- test/ReactApplicationTest.php | 68 ++++++++++++++++++-- test/ServerFactoryTest.php | 35 ++++++++++ test/SocketFactoryTest.php | 30 +++++++++ 12 files changed, 316 insertions(+), 14 deletions(-) create mode 100644 src/LoopFactory.php create mode 100644 src/ServerFactory.php create mode 100644 src/SocketFactory.php create mode 100644 test/LoopFactoryTest.php create mode 100644 test/ServerFactoryTest.php create mode 100644 test/SocketFactoryTest.php diff --git a/src/Container/Config/ConfigProvider.php b/src/Container/Config/ConfigProvider.php index 0dc5a2d..9c68a35 100644 --- a/src/Container/Config/ConfigProvider.php +++ b/src/Container/Config/ConfigProvider.php @@ -3,7 +3,13 @@ namespace Antidot\React\Container\Config; use Antidot\Application\Http\Application; +use Antidot\React\LoopFactory; use Antidot\React\ReactApplicationFactory; +use Antidot\React\ServerFactory; +use Antidot\React\SocketFactory; +use React\EventLoop\LoopInterface; +use React\Http\Server; +use React\Socket\Server as Socket; class ConfigProvider { @@ -13,8 +19,12 @@ public function __invoke(): array 'dependencies' => [ 'factories' => [ Application::class => ReactApplicationFactory::class, + LoopInterface::class => LoopFactory::class, + Server::class => ServerFactory::class, + Socket::class => SocketFactory::class, ], - ] + ], + 'server' => [] ]; } } diff --git a/src/LoopFactory.php b/src/LoopFactory.php new file mode 100644 index 0000000..7e1d9c4 --- /dev/null +++ b/src/LoopFactory.php @@ -0,0 +1,17 @@ +routeFactory = $routeFactory; $this->middlewareFactory = $middlewareFactory; $this->router = $router; $this->pipeline = $pipeline; + $this->errorResponseGenerator = $errorResponseGenerator; } public function pipe(string $middlewareName): void @@ -80,7 +87,23 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface { return new PromiseResponse(resolve($request) ->then( - fn(ServerRequestInterface $request): ResponseInterface => $this->pipeline->process($request, $handler) + function (ServerRequestInterface $request) use ($handler): PromiseInterface { + $response = new PromiseResponse( + resolve($request) + ->then(static function (ServerRequestInterface $request): ServerRequestInterface { + return $request->withAttribute('request_id', Uuid::uuid4()->toString()); + }) + ->then(function (ServerRequestInterface $request) use ($handler): ResponseInterface { + try { + return $this->pipeline->process($request, $handler); + } catch (Throwable $exception) { + return $this->errorResponseGenerator->__invoke($exception); + } + }) + ); + + return resolve($response); + } )); } @@ -88,7 +111,23 @@ public function handle(ServerRequestInterface $request): ResponseInterface { return new PromiseResponse(resolve($request) ->then( - fn (ServerRequestInterface $request): ResponseInterface => $this->pipeline->handle($request) + function (ServerRequestInterface $request): PromiseInterface { + $response = new PromiseResponse( + resolve($request) + ->then(static function (ServerRequestInterface $request): ServerRequestInterface { + return $request->withAttribute('request_id', Uuid::uuid4()->toString()); + }) + ->then(function (ServerRequestInterface $request): ResponseInterface { + try { + return $this->pipeline->handle($request); + } catch (Throwable $exception) { + return $this->errorResponseGenerator->__invoke($exception); + } + }) + ); + + return resolve($response); + } )); } diff --git a/src/ReactApplicationFactory.php b/src/ReactApplicationFactory.php index ac3cc53..bc54961 100644 --- a/src/ReactApplicationFactory.php +++ b/src/ReactApplicationFactory.php @@ -4,6 +4,7 @@ namespace Antidot\React; +use Antidot\Application\Http\Response\ErrorResponseGenerator; use Antidot\Application\Http\RouteFactory; use Antidot\Application\Http\Router; use Antidot\Container\MiddlewareFactory; @@ -19,12 +20,15 @@ public function __invoke(ContainerInterface $container): ReactApplication $middlewareFactory = $container->get(MiddlewareFactory::class); /** @var RouteFactory $routeFactory */ $routeFactory = $container->get(RouteFactory::class); + /** @var ErrorResponseGenerator $errorResponseGenerator */ + $errorResponseGenerator = $container->get(ErrorResponseGenerator::class); return new ReactApplication( new MiddlewarePipeline(), $router, $middlewareFactory, - $routeFactory + $routeFactory, + $errorResponseGenerator ); } } diff --git a/src/ServerFactory.php b/src/ServerFactory.php new file mode 100644 index 0000000..6a7b029 --- /dev/null +++ b/src/ServerFactory.php @@ -0,0 +1,42 @@ +get(Application::class); + assert($application instanceof ReactApplication); + /** @var LoopInterface $loop */ + $loop = $container->get(LoopInterface::class); + /** @var array $globalConfig */ + $globalConfig = $container->get('config'); + /** @var array $config */ + $config = $globalConfig['server']; + + $server = new Server( + $loop, + new StreamingRequestMiddleware(), + new LimitConcurrentRequestsMiddleware(($config['max_concurrency']) ?? 100), + new RequestBodyBufferMiddleware($config['buffer_size'] ?? 4 * 1024 * 1024), // 4 MiB + new RequestBodyParserMiddleware(), + static fn (ServerRequestInterface $request): ResponseInterface => $application->handle($request) + ); + + return $server; + } +} diff --git a/src/SocketFactory.php b/src/SocketFactory.php new file mode 100644 index 0000000..4924a02 --- /dev/null +++ b/src/SocketFactory.php @@ -0,0 +1,28 @@ +get(LoopInterface::class); + /** @var array $globalConfig */ + $globalConfig = $container->get('config'); + /** @var array $config */ + $config = $globalConfig['server']; + + return new Socket(sprintf( + '%s:%s', + $config['host'] ?? '0.0.0.0', + $config['port'] ?? '8080' + ), $loop); + } +} diff --git a/test/Container/Config/ConfigProviderTest.php b/test/Container/Config/ConfigProviderTest.php index 5e81e73..98b2aff 100644 --- a/test/Container/Config/ConfigProviderTest.php +++ b/test/Container/Config/ConfigProviderTest.php @@ -1,11 +1,17 @@ assertIsArray($configProvider()); $this->assertSame( - ['dependencies' => ['factories' => [Application::class => ReactApplicationFactory::class]]], + [ + 'dependencies' => [ + 'factories' => [ + Application::class => ReactApplicationFactory::class, + LoopInterface::class => LoopFactory::class, + Server::class => ServerFactory::class, + Socket::class => SocketFactory::class, + ] + ], + 'server' => [] + ], $configProvider(), ); } diff --git a/test/LoopFactoryTest.php b/test/LoopFactoryTest.php new file mode 100644 index 0000000..b3f812f --- /dev/null +++ b/test/LoopFactoryTest.php @@ -0,0 +1,20 @@ +createMock(ContainerInterface::class)); + $this->assertInstanceOf(StreamSelectLoop::class, $loop); + } +} diff --git a/test/ReactApplicationFactoryTest.php b/test/ReactApplicationFactoryTest.php index 3078a7c..07ec10f 100644 --- a/test/ReactApplicationFactoryTest.php +++ b/test/ReactApplicationFactoryTest.php @@ -4,6 +4,7 @@ namespace AntidotTest\React; +use Antidot\Application\Http\Response\ErrorResponseGenerator; use Antidot\Application\Http\Router; use Antidot\Application\Http\RouteFactory; use Antidot\Container\MiddlewareFactory; @@ -16,17 +17,19 @@ class ReactApplicationFactoryTest extends TestCase public function testItShouldCreateInstancesOfReactApplication(): void { $container = $this->createMock(ContainerInterface::class); - $container->expects($this->exactly(3)) + $container->expects($this->exactly(4)) ->method('get') ->withConsecutive( [Router::class], [MiddlewareFactory::class], [RouteFactory::class], + [ErrorResponseGenerator::class], ) ->willReturnOnConsecutiveCalls( $this->createMock(Router::class), $this->createMock(MiddlewareFactory::class), - $this->createMock(RouteFactory::class) + $this->createMock(RouteFactory::class), + $this->createMock(ErrorResponseGenerator::class) ); $factory = new ReactApplicationFactory(); diff --git a/test/ReactApplicationTest.php b/test/ReactApplicationTest.php index e09b9dc..b585fd6 100644 --- a/test/ReactApplicationTest.php +++ b/test/ReactApplicationTest.php @@ -4,11 +4,13 @@ namespace AntidotTest\React; +use Antidot\Application\Http\Response\ErrorResponseGenerator; use Antidot\Application\Http\RouteFactory; use Antidot\Application\Http\Router; use Antidot\Container\MiddlewareFactory; use Antidot\React\MiddlewarePipeline; use Antidot\React\ReactApplication; +use Exception; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; @@ -22,6 +24,7 @@ class ReactApplicationTest extends TestCase private Router $router; private MiddlewareFactory $middlewareFactory; private RouteFactory $routeFactory; + private ErrorResponseGenerator $errorResponseGenerator; protected function setUp(): void { @@ -29,6 +32,7 @@ protected function setUp(): void $this->router = $this->createMock(Router::class); $this->middlewareFactory = $this->createMock(MiddlewareFactory::class); $this->routeFactory = $this->createMock(RouteFactory::class); + $this->errorResponseGenerator = $this->createMock(ErrorResponseGenerator::class); } /** @dataProvider getRoutes */ @@ -44,7 +48,8 @@ public function testItShouldAddRoutesInSomeApplicationHttpMethod(string $method, $this->pipeline, $this->router, $this->middlewareFactory, - $this->routeFactory + $this->routeFactory, + $this->errorResponseGenerator ); $application->{$method}(...$params); @@ -65,7 +70,8 @@ public function testItShouldAddMiddlewareInApplication(): void $this->pipeline, $this->router, $this->middlewareFactory, - $this->routeFactory + $this->routeFactory, + $this->errorResponseGenerator ); $application->pipe('SomeMiddleware'); @@ -75,6 +81,9 @@ public function testItShouldHandleProcessThenReturnPromiseResponse(): void { $handler = $this->createMock(RequestHandlerInterface::class); $request = $this->createMock(ServerRequestInterface::class); + $request->expects($this->once()) + ->method('withAttribute') + ->willReturn($request); $this->pipeline->expects($this->once()) ->method('process'); @@ -82,7 +91,30 @@ public function testItShouldHandleProcessThenReturnPromiseResponse(): void $this->pipeline, $this->router, $this->middlewareFactory, - $this->routeFactory + $this->routeFactory, + $this->errorResponseGenerator + ); + + await($application->process($request, $handler), Factory::create()); + } + + public function testItShouldProcessFailingRequestThenReturnAnErrorPromiseResponse(): void + { + $handler = $this->createMock(RequestHandlerInterface::class); + $request = $this->createMock(ServerRequestInterface::class); + $request->expects($this->once()) + ->method('withAttribute') + ->willReturn($request); + $this->pipeline->expects($this->once()) + ->method('process') + ->willThrowException(new Exception('fail')); + + $application = new ReactApplication( + $this->pipeline, + $this->router, + $this->middlewareFactory, + $this->routeFactory, + $this->errorResponseGenerator ); await($application->process($request, $handler), Factory::create()); @@ -91,6 +123,9 @@ public function testItShouldHandleProcessThenReturnPromiseResponse(): void public function testItShouldHandleRequestThenReturnPromiseResponse(): void { $request = $this->createMock(ServerRequestInterface::class); + $request->expects($this->once()) + ->method('withAttribute') + ->willReturn($request); $this->pipeline->expects($this->once()) ->method('handle'); @@ -98,7 +133,29 @@ public function testItShouldHandleRequestThenReturnPromiseResponse(): void $this->pipeline, $this->router, $this->middlewareFactory, - $this->routeFactory + $this->routeFactory, + $this->errorResponseGenerator + ); + + await($application->handle($request), Factory::create()); + } + + public function testItShouldHandleFailingRequestThenReturnAnErrorPromiseResponse(): void + { + $request = $this->createMock(ServerRequestInterface::class); + $request->expects($this->once()) + ->method('withAttribute') + ->willReturn($request); + $this->pipeline->expects($this->once()) + ->method('handle') + ->willThrowException(new Exception('fail')); + + $application = new ReactApplication( + $this->pipeline, + $this->router, + $this->middlewareFactory, + $this->routeFactory, + $this->errorResponseGenerator ); await($application->handle($request), Factory::create()); @@ -113,7 +170,8 @@ public function testItSouldFailWhenTryToRun(): void $this->pipeline, $this->router, $this->middlewareFactory, - $this->routeFactory + $this->routeFactory, + $this->errorResponseGenerator ); $application->run(); diff --git a/test/ServerFactoryTest.php b/test/ServerFactoryTest.php new file mode 100644 index 0000000..0c7c28d --- /dev/null +++ b/test/ServerFactoryTest.php @@ -0,0 +1,35 @@ +createMock(ContainerInterface::class); + $container->expects($this->exactly(3)) + ->method('get') + ->withConsecutive([Application::class], [LoopInterface::class], ['config']) + ->willReturnOnConsecutiveCalls( + $this->createMock(ReactApplication::class), + $this->createMock(LoopInterface::class), + ['server' => [ + + ]] + ); + $factory = new ServerFactory(); + $server = $factory($container); + $this->assertInstanceOf(Server::class, $server); + } +} diff --git a/test/SocketFactoryTest.php b/test/SocketFactoryTest.php new file mode 100644 index 0000000..1fadca6 --- /dev/null +++ b/test/SocketFactoryTest.php @@ -0,0 +1,30 @@ +createMock(ContainerInterface::class); + $container->expects($this->exactly(2)) + ->method('get') + ->withConsecutive([LoopInterface::class], ['config']) + ->willReturnOnConsecutiveCalls( + $this->createMock(LoopInterface::class), + ['server' => []] + ); + + $factory = new SocketFactory(); + $socket = $factory($container); + $this->assertInstanceOf(Socket::class, $socket); + } +}