Skip to content

Commit

Permalink
Add WebSocket into Swoole and fix the issue in the console
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelnabil230 committed Dec 27, 2023
1 parent 577cc8d commit 5c5c4dd
Show file tree
Hide file tree
Showing 22 changed files with 375 additions and 59 deletions.
6 changes: 5 additions & 1 deletion bin/createSwooleServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
$config = $serverState['octaneConfig'];

try {
$serverClass = ($config['swoole']['enableWebSocket'] ?? false)
? \Swoole\Websocket\Server::class
: \Swoole\Http\Server::class;

$host = $serverState['host'] ?? '127.0.0.1';

$sock = filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? SWOOLE_SOCK_TCP : SWOOLE_SOCK_TCP6;

$server = new Swoole\Http\Server(
$server = new $serverClass(
$host,
$serverState['port'] ?? 8000,
$config['swoole']['mode'] ?? SWOOLE_PROCESS,
Expand Down
99 changes: 91 additions & 8 deletions bin/swoole-server
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@ use Laravel\Octane\RequestContext;
use Laravel\Octane\Swoole\Handlers\OnManagerStart;
use Laravel\Octane\Swoole\Handlers\OnServerStart;
use Laravel\Octane\Swoole\Handlers\OnWorkerStart;
use Laravel\Octane\Swoole\Handlers\OnWebSocketOpen;
use Laravel\Octane\Swoole\Handlers\OnWebSocketDisconnect;
use Laravel\Octane\Swoole\Handlers\OnWebSocketHandshake;
use Laravel\Octane\Swoole\Handlers\OnWebSocketMessage;
use Laravel\Octane\Swoole\ServerStateFile;
use Laravel\Octane\Swoole\SwooleExtension;
use Laravel\Octane\Swoole\WorkerState;
use Swoole\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\Server;
use Swoole\Timer;
use Swoole\WebSocket\Frame;

ini_set('display_errors', 'stderr');

Expand Down Expand Up @@ -62,7 +69,8 @@ $server->on('managerstart', function () use ($serverState) {
require_once __DIR__.'/../src/Swoole/SwooleExtension.php';

(new OnManagerStart(
new SwooleExtension, $serverState['appName']
new SwooleExtension,
$serverState['appName']
))();
});

Expand All @@ -89,9 +97,13 @@ $workerState->cacheTable = require __DIR__.'/createSwooleCacheTable.php';
$workerState->timerTable = $timerTable;
$workerState->tables = require __DIR__.'/createSwooleTables.php';

$server->on('workerstart', fn (Server $server, $workerId) =>
(fn ($basePath) => (new OnWorkerStart(
new SwooleExtension, $basePath, $serverState, $workerState
$server->on(
'workerstart',
fn (Server $server, $workerId) => (fn ($basePath) => (new OnWorkerStart(
new SwooleExtension,
$basePath,
$serverState,
$workerState
))($server, $workerId))($bootstrap($serverState))
);

Expand Down Expand Up @@ -129,6 +141,75 @@ $server->on('request', function ($request, $response) use ($server, $workerState
}
});

/*
|--------------------------------------------------------------------------
| Handle Websocket Events
|--------------------------------------------------------------------------
|
| The following callbacks manage websocket events when enabled
|
*/

if (($serverState['octaneConfig']['swoole']['enableWebSocket'])) {
/*
|--------------------------------------------------------------------------
| Handle Incoming WebSocket open
|--------------------------------------------------------------------------
|
| The following callback will handle all incoming new connections.
|
*/

$server->on('open', fn (Request $request, Response $response) => (new OnWebSocketOpen(
$server,
$serverState,
$workerState
))($request, $response));

/*
|--------------------------------------------------------------------------
| Handle Incoming WebSocket Connections
|--------------------------------------------------------------------------
|
| The following callback will handle all incoming connections.
|
*/

$server->on('handshake', fn (Request $request, Response $response) => (new OnWebSocketHandshake(
$server,
$serverState,
$workerState
))($request, $response));

/*
|--------------------------------------------------------------------------
| Handle Incoming WebSocket Messages
|--------------------------------------------------------------------------
|
| The following callback will handle all incoming messages.
|
*/

$server->on('message', fn (Server $server, Frame $frame) => (new OnWebSocketMessage(
$serverState,
$workerState
))($server, $frame));

/*
|--------------------------------------------------------------------------
| Handle Disconnect WebSocket Connections
|--------------------------------------------------------------------------
|
| The following callback will handle all disconnect connections.
|
*/

$server->on('disconnect', fn (Server $server, int $fd) => (new OnWebSocketDisconnect(
$serverState,
$workerState
))($server, $fd));
}

/*
|--------------------------------------------------------------------------
| Handle Tasks
Expand All @@ -140,10 +221,12 @@ $server->on('request', function ($request, $response) use ($server, $workerState
|
*/

$server->on('task', fn (Server $server, int $taskId, int $fromWorkerId, $data) =>
$server->on(
'task',
fn (Server $server, int $taskId, int $fromWorkerId, $data) =>
$data === 'octane-tick'
? $workerState->worker->handleTick()
: $workerState->worker->handleTask($data)
? $workerState->worker->handleTick()
: $workerState->worker->handleTask($data)
);

$server->on('finish', fn (Server $server, int $taskId, $result) => $result);
Expand Down
7 changes: 4 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
],
"require": {
"php": "^8.1.0",
"laravel/framework": "^10.10.1",
"laminas/laminas-diactoros": "^3.0",
"laravel/framework": "^10.10.1",
"laravel/serializable-closure": "^1.3.0",
"nesbot/carbon": "^2.66.0",
"symfony/psr-http-message-bridge": "^2.2.0"
Expand All @@ -32,8 +32,9 @@
"orchestra/testbench": "^8.5.2",
"phpstan/phpstan": "^1.10.15",
"phpunit/phpunit": "^10.1.3",
"spiral/roadrunner-cli": "^2.5.0",
"spiral/roadrunner-http": "^3.0.1",
"spiral/roadrunner-cli": "^2.5.0"
"swoole/ide-helper": "^5.1"
},
"bin": [
"bin/roadrunner-worker",
Expand Down Expand Up @@ -75,6 +76,6 @@
"config": {
"sort-packages": true
},
"minimum-stability": "stable",
"minimum-stability": "dev",
"prefer-stable": true
}
38 changes: 31 additions & 7 deletions src/Commands/Concerns/InteractsWithServers.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,25 @@ protected function runServer($server, $inspector, $type)
sleep(1);
}

$this->writeServerRunningMessage();
$this->writeServerRunningMessage($type);

$watcher = $this->startServerWatcher();

try {
while ($server->isRunning()) {
$this->writeServerOutput($server);

if ($watcher->isRunning() &&
$watcher->getIncrementalOutput()) {
if (
$watcher->isRunning() &&
$watcher->getIncrementalOutput()
) {
$this->info('Application change detected. Restarting workers…');

$inspector->reloadServer();
} elseif ($watcher->isTerminated()) {
$this->error(
'Watcher process has terminated. Please ensure Node and chokidar are installed.'.PHP_EOL.
$watcher->getErrorOutput()
$watcher->getErrorOutput()
);

return 1;
Expand Down Expand Up @@ -94,19 +96,41 @@ public function __call($method, $parameters)
*
* @return void
*/
protected function writeServerRunningMessage()
protected function writeServerRunningMessage(string $type)
{
$this->info('Server running…');

$urls = [
" Local: <fg=white;options=bold>{$this->uri()}</>",
];

if ($type === 'swoole' && config('octane.swoole.enableWebSocket', false)) {
$uri = (config('octane.https', false) ? 'wss://' : 'ws://').$this->getHost().':'.$this->getPort();

$urls += [
'',
'',
" WebSocket: <fg=white;options=bold>$uri</>",
];
}

$this->output->writeln([
'',
' Local: <fg=white;options=bold>'.($this->hasOption('https') && $this->option('https') ? 'https://' : 'http://').$this->getHost().':'.$this->getPort().' </>',
...$urls,
'',
' <fg=yellow>Press Ctrl+C to stop the server</>',
' <fg=yellow;options=bold>Press Ctrl+C to stop the server</>',
'',
]);
}

/**
* For the given URI, retrieve the host and port.
*/
protected function uri(): string
{
return (config('octane.https', false) ? 'https://' : 'http://').$this->getHost().':'.$this->getPort();
}

/**
* Retrieve the given server output and flush it.
*
Expand Down
2 changes: 0 additions & 2 deletions src/Commands/StartCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ class StartCommand extends Command implements SignalableCommandInterface
{--max-requests=500 : The number of requests to process before reloading the server}
{--rr-config= : The path to the RoadRunner .rr.yaml file}
{--caddyfile= : The path to the FrankenPHP Caddyfile file}
{--https : Enable HTTPS, HTTP/2, and HTTP/3, and automatically generate and renew certificates [FrankenPHP only]}
{--watch : Automatically reload the server when the application is modified}
{--poll : Use file system polling while watching in order to watch files over a network}
{--log-level= : Log messages at or above the specified log level}';
Expand Down Expand Up @@ -107,7 +106,6 @@ protected function startFrankenPhpServer()
'--workers' => $this->option('workers'),
'--max-requests' => $this->option('max-requests'),
'--caddyfile' => $this->option('caddyfile'),
'--https' => $this->option('https'),
'--watch' => $this->option('watch'),
'--poll' => $this->option('poll'),
'--log-level' => $this->option('log-level'),
Expand Down
10 changes: 1 addition & 9 deletions src/Commands/StartFrankenPhpCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ class StartFrankenPhpCommand extends Command implements SignalableCommandInterfa
{--workers=auto : The number of workers that should be available to handle requests}
{--max-requests=500 : The number of requests to process before reloading the server}
{--caddyfile= : The path to the FrankenPHP Caddyfile file}
{--https : Enable HTTPS, HTTP/2, and HTTP/3, and automatically generate and renew certificates}
{--watch : Automatically reload the server when the application is modified}
{--poll : Use file system polling while watching in order to watch files over a network}
{--log-level= : Log messages at or above the specified log level}';
Expand Down Expand Up @@ -72,13 +71,6 @@ public function handle(ServerProcessInspector $inspector, ServerStateFile $serve

$this->forgetEnvironmentVariables();

$host = $this->option('host');
$port = $this->getPort();

$serverName = $this->option('https')
? "https://$host:$port"
: "http://:$port";

$process = tap(new Process([
$frankenphpBinary,
'run',
Expand All @@ -93,7 +85,7 @@ public function handle(ServerProcessInspector $inspector, ServerStateFile $serve
'CADDY_SERVER_ADMIN_PORT' => $this->adminPort(),
'CADDY_SERVER_LOG_LEVEL' => $this->option('log-level') ?: (app()->environment('local') ? 'INFO' : 'WARN'),
'CADDY_SERVER_LOGGER' => 'json',
'CADDY_SERVER_SERVER_NAME' => $serverName,
'CADDY_SERVER_SERVER_NAME' => $this->uri(),
'CADDY_SERVER_WORKER_COUNT' => $this->workerCount() ?: '',
'CADDY_SERVER_EXTRA_DIRECTIVES' => $this->buildMercureConfig(),
]));
Expand Down
9 changes: 6 additions & 3 deletions src/Concerns/ProvidesConcurrencySupport.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use Laravel\Octane\Swoole\ServerStateFile;
use Laravel\Octane\Swoole\SwooleHttpTaskDispatcher;
use Laravel\Octane\Swoole\SwooleTaskDispatcher;
use Swoole\Http\Server;

trait ProvidesConcurrencySupport
{
Expand All @@ -33,10 +32,14 @@ public function concurrently(array $tasks, int $waitMilliseconds = 3000)
*/
public function tasks()
{
$serverClass = config('octane.swoole.enableWebSocket', false)
? \Swoole\Websocket\Server::class
: \Swoole\Http\Server::class;

return match (true) {
app()->bound(DispatchesTasks::class) => app(DispatchesTasks::class),
app()->bound(Server::class) => new SwooleTaskDispatcher,
class_exists(Server::class) => (fn (array $serverState) => new SwooleHttpTaskDispatcher(
app()->bound($serverClass) => new SwooleTaskDispatcher,
class_exists($serverClass) => (fn (array $serverState) => new SwooleHttpTaskDispatcher(
$serverState['state']['host'] ?? '127.0.0.1',
$serverState['state']['port'] ?? '8000',
new SequentialTaskDispatcher
Expand Down
16 changes: 16 additions & 0 deletions src/Events/WebSocketDisconnect.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Laravel\Octane\Events;

use Illuminate\Foundation\Application;
use Swoole\WebSocket\Server;

class WebSocketDisconnect
{
public function __construct(
public Application $app,
public Server $server,
public int $fd
) {
}
}
17 changes: 17 additions & 0 deletions src/Events/WebSocketMessage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Laravel\Octane\Events;

use Illuminate\Foundation\Application;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;

class WebSocketMessage
{
public function __construct(
public Application $app,
public Server $server,
public Frame $frame
) {
}
}
15 changes: 15 additions & 0 deletions src/Events/WebSocketOpen.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Laravel\Octane\Events;

use Illuminate\Foundation\Application;
use Swoole\WebSocket\Server;

class WebSocketOpen
{
public function __construct(
public Application $app,
public Server $server,
) {
}
}
Loading

0 comments on commit 5c5c4dd

Please sign in to comment.