Skip to content

Commit c3b4c34

Browse files
leocavalcanteNyholm
authored andcommitted
Adding PSR RequestHandlerInterface for Swoole
1 parent 834b864 commit c3b4c34

File tree

7 files changed

+181
-1
lines changed

7 files changed

+181
-1
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,30 @@ return function () {
3636
};
3737
```
3838

39+
### PSR
40+
41+
```php
42+
// public/index.php
43+
44+
use Nyholm\Psr7\Response;
45+
use Psr\Http\Message\ResponseInterface;
46+
use Psr\Http\Message\ServerRequestInterface;
47+
use Psr\Http\Server\RequestHandlerInterface;
48+
49+
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
50+
51+
class App implements RequestHandlerInterface {
52+
public function handle(ServerRequestInterface $request): ResponseInterface {
53+
$name = $request->getQueryParams()['name'] ?? 'World';
54+
return new Response(200, ['Server' => 'swoole-runtime'], "Hello, $name!");
55+
}
56+
}
57+
58+
return function(): RequestHandlerInterface {
59+
return new App();
60+
};
61+
```
62+
3963
### Symfony
4064

4165
```php

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
}
1111
],
1212
"require": {
13+
"nyholm/psr7": "^1.4",
14+
"psr/http-server-handler": "^1.0",
1315
"symfony/runtime": "^5.3 || ^6.0"
1416
},
1517
"require-dev": {

src/RequestHandlerRunner.php

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
namespace Runtime\Swoole;
4+
5+
use Psr\Http\Server\RequestHandlerInterface;
6+
use Swoole\Http\Request;
7+
use Swoole\Http\Response;
8+
use Symfony\Component\Runtime\RunnerInterface;
9+
10+
class RequestHandlerRunner implements RunnerInterface
11+
{
12+
/**
13+
* @var int
14+
*/
15+
private const CHUNK_SIZE = 2097152; // 2MB
16+
/**
17+
* @var ServerFactory
18+
*/
19+
private $serverFactory;
20+
/**
21+
* @var RequestHandlerInterface
22+
*/
23+
private $application;
24+
25+
public function __construct(ServerFactory $serverFactory, RequestHandlerInterface $application)
26+
{
27+
$this->serverFactory = $serverFactory;
28+
$this->application = $application;
29+
}
30+
31+
public function run(): int
32+
{
33+
$this->serverFactory->createServer([$this, 'handle'])->start();
34+
35+
return 0;
36+
}
37+
38+
public function handle(Request $request, Response $response): void
39+
{
40+
$psrRequest = (new \Nyholm\Psr7\ServerRequest(
41+
$request->getMethod(),
42+
$request->server['request_uri'] ?? '/',
43+
array_change_key_case($request->server ?? [], CASE_UPPER),
44+
$request->rawContent(),
45+
'1.1',
46+
$request->server ?? []
47+
))
48+
->withQueryParams($request->get ?? []);
49+
50+
$psrResponse = $this->application->handle($psrRequest);
51+
52+
$response->setStatusCode($psrResponse->getStatusCode(), $psrResponse->getReasonPhrase());
53+
54+
foreach ($psrResponse->getHeaders() as $name => $values) {
55+
foreach ($values as $value) {
56+
$response->setHeader($name, $value);
57+
}
58+
}
59+
60+
$body = $psrResponse->getBody();
61+
$body->rewind();
62+
63+
if ($body->isReadable()) {
64+
if ($body->getSize() <= self::CHUNK_SIZE) {
65+
if ($contents = $body->getContents()) {
66+
$response->write($contents);
67+
}
68+
} else {
69+
while (!$body->eof() && ($contents = $body->read(self::CHUNK_SIZE))) {
70+
$response->write($contents);
71+
}
72+
}
73+
74+
$response->end();
75+
} else {
76+
$response->end((string) $body);
77+
}
78+
79+
$body->close();
80+
}
81+
}

src/Runtime.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Runtime\Swoole;
44

55
use Illuminate\Contracts\Http\Kernel;
6+
use Psr\Http\Server\RequestHandlerInterface;
67
use Symfony\Component\HttpKernel\HttpKernelInterface;
78
use Symfony\Component\Runtime\RunnerInterface;
89
use Symfony\Component\Runtime\SymfonyRuntime;
@@ -37,6 +38,10 @@ public function getRunner(?object $application): RunnerInterface
3738
return new LaravelRunner($this->serverFactory, $application);
3839
}
3940

41+
if ($application instanceof RequestHandlerInterface) {
42+
return new RequestHandlerRunner($this->serverFactory, $application);
43+
}
44+
4045
return parent::getRunner($application);
4146
}
4247
}

tests/E2E/StaticFileHandlerTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44

55
use PHPUnit\Framework\TestCase;
66
use function Swoole\Coroutine\Http\get;
7+
use function Swoole\Coroutine\run;
78

89
class StaticFileHandlerTest extends TestCase
910
{
1011
public function testSwooleServerHandlesStaticFiles(): void
1112
{
12-
\Co\run(static function (): void {
13+
run(static function (): void {
1314
self::assertSame("Static file\n", get('http://localhost:8001/file.txt')->getBody());
1415
});
1516
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace Runtime\Swoole;
4+
5+
use Nyholm\Psr7\Stream;
6+
use PHPUnit\Framework\TestCase;
7+
use Psr\Http\Message\ResponseInterface;
8+
use Psr\Http\Server\RequestHandlerInterface;
9+
use Swoole\Http\Request;
10+
use Swoole\Http\Response;
11+
use Swoole\Http\Server;
12+
13+
class RequestHandlerRunnerTest extends TestCase
14+
{
15+
public function testRun(): void
16+
{
17+
$server = $this->createMock(Server::class);
18+
$factory = $this->createMock(ServerFactory::class);
19+
$application = $this->createMock(RequestHandlerInterface::class);
20+
21+
$factory->expects(self::once())->method('createServer')->willReturn($server);
22+
$server->expects(self::once())->method('start');
23+
24+
$runner = new RequestHandlerRunner($factory, $application);
25+
26+
self::assertSame(0, $runner->run());
27+
}
28+
29+
public function testHandle(): void
30+
{
31+
$factory = $this->createMock(ServerFactory::class);
32+
$application = $this->createMock(RequestHandlerInterface::class);
33+
$psrResponse = $this->createMock(ResponseInterface::class);
34+
$request = $this->createMock(Request::class);
35+
$response = $this->createMock(Response::class);
36+
37+
$request->expects(self::once())->method('getMethod')->willReturn('POST');
38+
$request->expects(self::once())->method('rawContent')->willReturn('Test');
39+
40+
$application->expects(self::once())->method('handle')->willReturn($psrResponse);
41+
42+
$psrResponse->expects(self::once())->method('getHeaders')->willReturn([
43+
'X-Test' => ['Swoole-Runtime'],
44+
]);
45+
$psrResponse->expects(self::once())->method('getBody')->willReturn(Stream::create('Test'));
46+
47+
$response->expects(self::once())->method('setHeader')->with('X-Test', 'Swoole-Runtime');
48+
$response->expects(self::once())->method('write')->with('Test');
49+
$response->expects(self::once())->method('end')->with(null);
50+
51+
$runner = new RequestHandlerRunner($factory, $application);
52+
$runner->handle($request, $response);
53+
}
54+
}

tests/Unit/RuntimeTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
use Illuminate\Contracts\Http\Kernel;
66
use PHPUnit\Framework\TestCase;
7+
use Psr\Http\Server\RequestHandlerInterface;
78
use Runtime\Swoole\CallableRunner;
89
use Runtime\Swoole\LaravelRunner;
10+
use Runtime\Swoole\RequestHandlerRunner;
911
use Runtime\Swoole\Runtime;
1012
use Runtime\Swoole\SymfonyRunner;
1113
use Symfony\Component\HttpKernel\HttpKernelInterface;
@@ -47,6 +49,17 @@ public function testGetRunnerCreatesARunnerForLaravel(): void
4749
self::assertInstanceOf(LaravelRunner::class, $runner);
4850
}
4951

52+
public function testGetRunnerCreatesARunnerForRequestHandlers(): void
53+
{
54+
$options = [];
55+
$runtime = new Runtime($options);
56+
57+
$application = $this->createMock(RequestHandlerInterface::class);
58+
$runner = $runtime->getRunner($application);
59+
60+
self::assertInstanceOf(RequestHandlerRunner::class, $runner);
61+
}
62+
5063
public function testGetRunnerFallbacksToClosureRunner(): void
5164
{
5265
$options = [];

0 commit comments

Comments
 (0)