Skip to content

Commit

Permalink
[HttpServer] Add ServerRequestRunner implementation and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
il-masaru-yamagishi committed Aug 24, 2024
1 parent 38ed035 commit 0bc6328
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 9 deletions.
13 changes: 13 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,19 @@
"nesbot/carbon": "Required when you use CarbonImmutable for Clock",
"spiral/roadrunner-http": "Required when you use RoadRunnerHttpDispatcher"
},
"provide": {
"psr/cache-implementation": "3.0.0",
"psr/clock-implementation": "1.0",
"psr/container-implementation": "2.0.2",
"psr/event-dispatcher-implementation": "1.0",
"psr/http-client-implementation": "1.0.3",
"psr/http-factory-implementation": "1.1.0",
"psr/http-message-implementation": "2.0",
"psr/http-server-handler-implementation": "1.0.2",
"psr/http-server-middleware-implementation": "1.0.2",
"psr/log-implementation": "3.0",
"psr/simple-cache-implementation": "3.0.0"
},
"scripts": {
"lint": [
"./tools/phpstan analyse src",
Expand Down
41 changes: 36 additions & 5 deletions src/HttpServer/ServerRequestRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,61 @@

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

/**
* PSR-15 ServerRequest runner
* PSR-15 ServerRequest runner Last In First Out middlewares
* @package Rayleigh\HttpServer
* @link https://github.com/httpsoft/http-runner/
* @link https://github.com/relayphp/Relay.Relay/
* @link https://github.com/slimphp/Slim/blob/4.x/Slim/MiddlewareDispatcher.php
* @example
* ```php
* return static function (ServerRequestInterface $request): ResponseInterface {
* $middlewares = []; // Add PSR-15 compatible middlewares
* return (new ServerRequestRunner($middlewares))->handle($request);
* $middlewares = ...; // Add MiddlewareInterface list
* $kernel = ... // RequestHandlerInterface
* return (new ServerRequestRunner($middlewares, $kernel))->handle($request);
* };
*/
class ServerRequestRunner implements RequestHandlerInterface
{
protected RequestHandlerInterface $handler;

/**
* Constructor
* @param iterable<mixed> $middlewares
* @param RequestHandlerInterface $handler
*/
public function __construct() {}
public function __construct(iterable $middlewares, RequestHandlerInterface $handler)
{
// Set main handler
$this->handler = $handler;

// Set middleware stack
foreach ($middlewares as $middleware) {
if ($middleware instanceof MiddlewareInterface === false) {
throw new \InvalidArgumentException('Middleware must be an instance of ' . MiddlewareInterface::class);
}

$next = $this->handler;
$this->handler = new class ($middleware, $next) implements RequestHandlerInterface {
public function __construct(
private readonly MiddlewareInterface $middleware,
private readonly RequestHandlerInterface $next,
) {
}

public function handle(ServerRequestInterface $request): ResponseInterface
{
return $this->middleware->process($request, $this->next);
}
};
}
}

public function handle(ServerRequestInterface $request): ResponseInterface
{
throw new \RuntimeException('Not implemented');
return $this->handler->handle($request);
}
}
94 changes: 90 additions & 4 deletions src/HttpServer/Tests/ServerRequestRunnerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Rayleigh\HttpServer\ServerRequestRunner;

/**
Expand All @@ -23,12 +26,95 @@
final class ServerRequestRunnerTest extends TestCase
{
#[Test]
public function testFailedHandleNoHandler(): void
public function testEmptyMiddleware(): void
{
$this->expectException(\RuntimeException::class);
/** @var ResponseInterface $response */
$response = self::createStub(ResponseInterface::class);
/** @var \PHPUnit\Framework\MockObject\MockObject&RequestHandlerInterface $handler */
$handler = $this->createMock(RequestHandlerInterface::class);
$handler->expects(self::once())
->method('handle')
->willReturn($response);
$runner = new ServerRequestRunner([], $handler);

$runner = new ServerRequestRunner();
/** @var ServerRequestInterface $request */
$request = self::createStub(ServerRequestInterface::class);

$runner->handle($this->createStub(ServerRequestInterface::class));
$actual = $runner->handle($request);

self::assertSame($response, $actual);
}

#[Test]
public function testMiddlewareStackLastInFirstOut(): void
{
/** @var \PHPUnit\Framework\MockObject\MockObject&ServerRequestInterface $request1 */
$request1 = $this->createMock(ServerRequestInterface::class);
$request1->expects(self::once())
->method('withAttribute')
->with('test2', 'test2')
->willReturn($request2 = $this->createMock(ServerRequestInterface::class));
$request2->expects(self::once())
->method('withAttribute')
->with('test1', 'test1')
->willReturn($request3 = $this->createMock(ServerRequestInterface::class));

$response1 = $this->createMock(ResponseInterface::class);
$response1->expects(self::once())
->method('withAddedHeader')
->with('X-Test', 'Test1')
->willReturn($response2 = $this->createMock(ResponseInterface::class));

$response2->expects(self::once())
->method('withAddedHeader')
->with('X-Test', 'Test2')
->willReturn($response3 = $this->createMock(ResponseInterface::class));

$middleware1 = new class () implements MiddlewareInterface {
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$response = $handler->handle($request->withAttribute('test1', 'test1'));
return $response->withAddedHeader('X-Test', 'Test1');
}
};
$middleware2 = new class () implements MiddlewareInterface {
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$response = $handler->handle($request->withAttribute('test2', 'test2'));
return $response->withAddedHeader('X-Test', 'Test2');
}
};

/** @var \PHPUnit\Framework\MockObject\MockObject&RequestHandlerInterface $handler */
$handler = $this->createMock(RequestHandlerInterface::class);
$handler->expects(self::once())
->method('handle')
->willReturn($response1);
$runner = new ServerRequestRunner([$middleware1, $middleware2], $handler);

$actual = $runner->handle($request1);

self::assertSame($response3, $actual);
}

#[Test]
public function testInvalidMiddleware(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Middleware must be an instance of ' . MiddlewareInterface::class);

$response = self::createStub(ResponseInterface::class);
$handler = new class ($response) implements RequestHandlerInterface {
public function __construct(private readonly ResponseInterface $response)
{
}

public function handle(ServerRequestInterface $request): ResponseInterface
{
return $this->response;
}
};

new ServerRequestRunner(['foo'], $handler);
}
}

0 comments on commit 0bc6328

Please sign in to comment.