Skip to content

Commit 834b864

Browse files
Adding Swoole's Server mode and settings (#59)
* Adding Swoole's Server mode and settings * Avoids Swoole constant definition * Fix code style * Refactor using factory class pattern * Add unit tests with 100% coverage * Fix pair with CI tools PHPUnit 8.5 and composer normalizer * Add Swoole to CI * Fix conflicting PHPUnit versions * Refactor unit tests to a self-contained directory * Tests end-to-end tests * Fix code style * Remove CI tool from Composer
1 parent caafeb0 commit 834b864

19 files changed

+504
-111
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
/composer.lock
2+
/phpunit.xml
13
/vendor/
2-
composer.lock

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,37 @@ return function (array $context) {
4949
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
5050
};
5151
```
52+
53+
## Using Options
54+
55+
You can define some configurations using Symfony's Runtime `APP_RUNTIME_OPTIONS` API.
56+
57+
| Option | Description | Default |
58+
| --- | --- | --- |
59+
| `host` | The host where the server should binds to (precedes `SWOOLE_HOST` environment variable) | `127.0.0.1` |
60+
| `port` | The port where the server should be listing (precedes `SWOOLE_PORT` environment variable) | `8000` |
61+
| `mode` | Swoole's server mode (precedes `SWOOLE_MODE` environment variable) | `SWOOLE_PROCESS` |
62+
| `settings` | All Swoole's server settings ([swoole.co.uk/docs/modules/swoole-server/configuration](https://www.swoole.co.uk/docs/modules/swoole-server/configuration)) | `[]` |
63+
64+
```php
65+
// public/index.php
66+
67+
use App\Kernel;
68+
69+
$_SERVER['APP_RUNTIME_OPTIONS'] = [
70+
'host' => '0.0.0.0',
71+
'port' => 9501,
72+
'mode' => SWOOLE_BASE,
73+
'settings' => [
74+
\Swoole\Constant::OPTION_WORKER_NUM => swoole_cpu_num() * 2,
75+
\Swoole\Constant::OPTION_ENABLE_STATIC_HANDLER => true,
76+
\Swoole\Constant::OPTION_DOCUMENT_ROOT => dirname(__DIR__).'/public'
77+
],
78+
];
79+
80+
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
81+
82+
return function (array $context) {
83+
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
84+
};
85+
```

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"symfony/runtime": "^5.3 || ^6.0"
1414
},
1515
"require-dev": {
16+
"illuminate/http": "^8.48",
1617
"swoole/ide-helper": "^4.6",
1718
"symfony/http-foundation": "^4.4 || ^5.2 || ^6.0",
1819
"symfony/http-kernel": "^4.4 || ^5.2 || ^6.0",

phpunit.xml.dist

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3-
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
3+
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
44
backupGlobals="false"
55
colors="true"
66
bootstrap="vendor/autoload.php"
@@ -11,9 +11,13 @@
1111
<ini name="error_reporting" value="-1"/>
1212
</php>
1313
<testsuites>
14-
<testsuite name="Test Suite">
15-
<directory>./tests</directory>
16-
<directory suffix=".phpt">./tests/phpt</directory>
14+
<testsuite name="Unit">
15+
<directory>./tests/Unit</directory>
1716
</testsuite>
1817
</testsuites>
18+
<filter>
19+
<whitelist processUncoveredFilesFromWhitelist="true">
20+
<directory suffix=".php">src</directory>
21+
</whitelist>
22+
</filter>
1923
</phpunit>

src/CallableRunner.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Runtime\Swoole;
4+
5+
use Symfony\Component\Runtime\RunnerInterface;
6+
7+
/**
8+
* A simple runner that will run a callable.
9+
*
10+
* @author Tobias Nyholm <[email protected]>
11+
*/
12+
class CallableRunner implements RunnerInterface
13+
{
14+
/** @var ServerFactory */
15+
private $serverFactory;
16+
/** @var callable */
17+
private $application;
18+
19+
public function __construct(ServerFactory $serverFactory, callable $application)
20+
{
21+
$this->serverFactory = $serverFactory;
22+
$this->application = $application;
23+
}
24+
25+
public function run(): int
26+
{
27+
$this->serverFactory->createServer($this->application)->start();
28+
29+
return 0;
30+
}
31+
}

src/LaravelRunner.php

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use Illuminate\Http\Request as LaravelRequest;
77
use Swoole\Http\Request;
88
use Swoole\Http\Response;
9-
use Swoole\Http\Server;
109
use Symfony\Component\HttpFoundation\HeaderBag;
1110
use Symfony\Component\Runtime\RunnerInterface;
1211

@@ -17,45 +16,44 @@
1716
*/
1817
class LaravelRunner implements RunnerInterface
1918
{
19+
/** @var ServerFactory */
20+
private $serverFactory;
21+
/** @var Kernel */
2022
private $application;
21-
private $port;
22-
private $host;
2323

24-
public function __construct(Kernel $application, $host, $port)
24+
public function __construct(ServerFactory $serverFactory, Kernel $application)
2525
{
26+
$this->serverFactory = $serverFactory;
2627
$this->application = $application;
27-
$this->host = $host;
28-
$this->port = $port;
2928
}
3029

3130
public function run(): int
3231
{
33-
$server = new Server($this->host, $this->port);
34-
35-
$app = $this->application;
36-
37-
$server->on('request', function (Request $request, Response $response) use ($app) {
38-
// convert to HttpFoundation request
39-
$sfRequest = new LaravelRequest(
40-
$request->get ?? [],
41-
$request->post ?? [],
42-
[],
43-
$request->cookie ?? [],
44-
$request->files ?? [],
45-
array_change_key_case($request->server ?? [], CASE_UPPER),
46-
$request->rawContent()
47-
);
48-
$sfRequest->headers = new HeaderBag($request->header);
49-
50-
$sfResponse = $app->handle($sfRequest);
51-
foreach ($sfResponse->headers->all() as $name => $value) {
52-
$response->header($name, $value);
53-
}
54-
$response->end($sfResponse->getContent());
55-
});
56-
57-
$server->start();
32+
$this->serverFactory->createServer([$this, 'handle'])->start();
5833

5934
return 0;
6035
}
36+
37+
public function handle(Request $request, Response $response): void
38+
{
39+
// convert to HttpFoundation request
40+
$sfRequest = new LaravelRequest(
41+
$request->get ?? [],
42+
$request->post ?? [],
43+
[],
44+
$request->cookie ?? [],
45+
$request->files ?? [],
46+
array_change_key_case($request->server ?? [], CASE_UPPER),
47+
$request->rawContent()
48+
);
49+
$sfRequest->headers = new HeaderBag($request->header);
50+
51+
$sfResponse = $this->application->handle($sfRequest);
52+
foreach ($sfResponse->headers->all() as $name => $values) {
53+
foreach ($values as $value) {
54+
$response->header($name, $value);
55+
}
56+
}
57+
$response->end($sfResponse->getContent());
58+
}
6159
}

src/Runner.php

Lines changed: 0 additions & 36 deletions
This file was deleted.

src/Runtime.php

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,27 @@
1414
*/
1515
class Runtime extends SymfonyRuntime
1616
{
17-
public function __construct(array $options)
18-
{
19-
$options['swoole_host'] = $options['swoole_host'] ?? $_SERVER['SWOOLE_HOST'] ?? $_ENV['SWOOLE_HOST'] ?? '127.0.0.1';
20-
$options['swoole_port'] = $options['swoole_port'] ?? $_SERVER['SWOOLE_PORT'] ?? $_ENV['SWOOLE_PORT'] ?? 8000;
17+
/** @var ?ServerFactory */
18+
private $serverFactory;
2119

22-
parent::__construct($options);
20+
public function __construct(array $options, ?ServerFactory $serverFactory = null)
21+
{
22+
$this->serverFactory = $serverFactory ?? new ServerFactory($options);
23+
parent::__construct($this->serverFactory->getOptions());
2324
}
2425

2526
public function getRunner(?object $application): RunnerInterface
2627
{
2728
if (is_callable($application)) {
28-
return new Runner($application, $this->options['swoole_host'], $this->options['swoole_port']);
29+
return new CallableRunner($this->serverFactory, $application);
2930
}
3031

3132
if ($application instanceof HttpKernelInterface) {
32-
return new SymfonyRunner($application, $this->options['swoole_host'], $this->options['swoole_port']);
33+
return new SymfonyRunner($this->serverFactory, $application);
3334
}
3435

3536
if ($application instanceof Kernel) {
36-
return new LaravelRunner($application, $this->options['swoole_host'], $this->options['swoole_port']);
37+
return new LaravelRunner($this->serverFactory, $application);
3738
}
3839

3940
return parent::getRunner($application);

src/ServerFactory.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace Runtime\Swoole;
4+
5+
use Swoole\Http\Server;
6+
7+
/**
8+
* A factory for Swoole HTTP Servers.
9+
*
10+
* @author Tobias Nyholm <[email protected]>
11+
*/
12+
class ServerFactory
13+
{
14+
private const DEFAULT_OPTIONS = [
15+
'host' => '127.0.0.1',
16+
'port' => 8000,
17+
'mode' => 2, // SWOOLE_PROCESS
18+
'settings' => [],
19+
];
20+
21+
/** @var array */
22+
private $options;
23+
24+
public static function getDefaultOptions(): array
25+
{
26+
return self::DEFAULT_OPTIONS;
27+
}
28+
29+
public function __construct(array $options = [])
30+
{
31+
$options['host'] = $options['host'] ?? $_SERVER['SWOOLE_HOST'] ?? $_ENV['SWOOLE_HOST'] ?? self::DEFAULT_OPTIONS['host'];
32+
$options['port'] = $options['port'] ?? $_SERVER['SWOOLE_PORT'] ?? $_ENV['SWOOLE_PORT'] ?? self::DEFAULT_OPTIONS['port'];
33+
$options['mode'] = $options['mode'] ?? $_SERVER['SWOOLE_MODE'] ?? $_ENV['SWOOLE_MODE'] ?? self::DEFAULT_OPTIONS['mode'];
34+
35+
$this->options = array_replace_recursive(self::DEFAULT_OPTIONS, $options);
36+
}
37+
38+
public function createServer(callable $requestHandler): Server
39+
{
40+
$server = new Server($this->options['host'], (int) $this->options['port'], (int) $this->options['mode']);
41+
$server->set($this->options['settings']);
42+
$server->on('request', $requestHandler);
43+
44+
return $server;
45+
}
46+
47+
public function getOptions(): array
48+
{
49+
return $this->options;
50+
}
51+
}

src/SymfonyRunner.php

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
use Swoole\Http\Request;
66
use Swoole\Http\Response;
7-
use Swoole\Http\Server;
87
use Symfony\Component\HttpFoundation\HeaderBag;
98
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
109
use Symfony\Component\HttpKernel\HttpKernelInterface;
@@ -17,45 +16,44 @@
1716
*/
1817
class SymfonyRunner implements RunnerInterface
1918
{
19+
/** @var ServerFactory */
20+
private $serverFactory;
21+
/** @var HttpKernelInterface */
2022
private $application;
21-
private $port;
22-
private $host;
2323

24-
public function __construct(HttpKernelInterface $application, $host, $port)
24+
public function __construct(ServerFactory $serverFactory, HttpKernelInterface $application)
2525
{
26+
$this->serverFactory = $serverFactory;
2627
$this->application = $application;
27-
$this->host = $host;
28-
$this->port = $port;
2928
}
3029

3130
public function run(): int
3231
{
33-
$server = new Server($this->host, $this->port);
34-
35-
$app = $this->application;
36-
37-
$server->on('request', function (Request $request, Response $response) use ($app) {
38-
// convert to HttpFoundation request
39-
$sfRequest = new SymfonyRequest(
40-
$request->get ?? [],
41-
$request->post ?? [],
42-
[],
43-
$request->cookie ?? [],
44-
$request->files ?? [],
45-
array_change_key_case($request->server ?? [], CASE_UPPER),
46-
$request->rawContent()
47-
);
48-
$sfRequest->headers = new HeaderBag($request->header);
49-
50-
$sfResponse = $app->handle($sfRequest);
51-
foreach ($sfResponse->headers->all() as $name => $value) {
52-
$response->header($name, $value);
53-
}
54-
$response->end($sfResponse->getContent());
55-
});
56-
57-
$server->start();
32+
$this->serverFactory->createServer([$this, 'handle'])->start();
5833

5934
return 0;
6035
}
36+
37+
public function handle(Request $request, Response $response): void
38+
{
39+
// convert to HttpFoundation request
40+
$sfRequest = new SymfonyRequest(
41+
$request->get ?? [],
42+
$request->post ?? [],
43+
[],
44+
$request->cookie ?? [],
45+
$request->files ?? [],
46+
array_change_key_case($request->server ?? [], CASE_UPPER),
47+
$request->rawContent()
48+
);
49+
$sfRequest->headers = new HeaderBag($request->header);
50+
51+
$sfResponse = $this->application->handle($sfRequest);
52+
foreach ($sfResponse->headers->all() as $name => $values) {
53+
foreach ($values as $value) {
54+
$response->header($name, $value);
55+
}
56+
}
57+
$response->end($sfResponse->getContent());
58+
}
6159
}

0 commit comments

Comments
 (0)