laravel-websockets/src/Console/Commands/StartServer.php

333 lines
8.6 KiB
PHP
Raw Normal View History

2018-11-20 10:51:00 +00:00
<?php
2020-09-10 19:59:26 +00:00
namespace BeyondCode\LaravelWebSockets\Console\Commands;
2018-11-20 10:51:00 +00:00
2020-09-10 19:59:49 +00:00
use BeyondCode\LaravelWebSockets\Contracts\ChannelManager;
use BeyondCode\LaravelWebSockets\Facades\StatisticsCollector as StatisticsCollectorFacade;
2020-09-10 20:16:17 +00:00
use BeyondCode\LaravelWebSockets\Facades\WebSocketRouter;
2020-09-10 19:59:26 +00:00
use BeyondCode\LaravelWebSockets\Server\Loggers\ConnectionLogger;
use BeyondCode\LaravelWebSockets\Server\Loggers\HttpLogger;
use BeyondCode\LaravelWebSockets\Server\Loggers\WebSocketsLogger;
use BeyondCode\LaravelWebSockets\ServerFactory;
2020-03-04 09:58:39 +00:00
use Illuminate\Console\Command;
2020-09-10 19:59:26 +00:00
use Illuminate\Support\Facades\Cache;
2020-09-10 19:59:49 +00:00
use React\EventLoop\Factory as LoopFactory;
use React\EventLoop\LoopInterface;
use function React\Promise\all;
2018-12-03 15:29:47 +00:00
2020-09-10 19:59:26 +00:00
class StartServer extends Command
2018-11-20 10:51:00 +00:00
{
2020-08-18 17:21:22 +00:00
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'websockets:serve
{--host=0.0.0.0}
{--port=6001}
2020-09-10 19:59:26 +00:00
{--disable-statistics : Disable the statistics tracking.}
{--statistics-interval= : The amount of seconds to tick between statistics saving.}
{--debug : Forces the loggers to be enabled and thereby overriding the APP_DEBUG setting.}
{--loop : Programatically inject the loop.}
';
2018-11-20 10:51:00 +00:00
2020-08-18 17:21:22 +00:00
/**
* The console command description.
*
* @var string|null
*/
2020-09-10 19:59:26 +00:00
protected $description = 'Start the LaravelWebSockets server.';
2018-11-20 10:51:00 +00:00
2020-08-18 17:21:22 +00:00
/**
* Get the loop instance.
*
* @var \React\EventLoop\LoopInterface
*/
2018-12-03 12:33:20 +00:00
protected $loop;
2020-08-18 17:21:22 +00:00
/**
* The Pusher server instance.
*
* @var \Ratchet\Server\IoServer
*/
public $server;
/**
* Initialize the command.
*
* @return void
*/
2018-12-03 12:33:20 +00:00
public function __construct()
{
parent::__construct();
$this->loop = LoopFactory::create();
}
2020-08-18 17:21:22 +00:00
/**
* Run the command.
*
* @return void
*/
2018-11-20 10:51:00 +00:00
public function handle()
{
$this->laravel->singleton(LoopInterface::class, function () {
return $this->loop;
});
2020-09-10 19:59:26 +00:00
$this->configureLoggers();
2018-11-24 00:25:40 +00:00
2020-09-10 19:59:26 +00:00
$this->configureManagers();
2018-12-03 13:46:51 +00:00
2020-09-10 19:59:26 +00:00
$this->configureStatistics();
2020-09-10 19:59:26 +00:00
$this->configureRestartTimer();
2018-12-03 13:46:51 +00:00
2020-09-11 05:29:48 +00:00
$this->configureRoutes();
$this->configurePcntlSignal();
2020-09-15 09:30:17 +00:00
$this->configurePongTracker();
2020-09-10 19:59:26 +00:00
$this->startServer();
2018-12-03 13:46:51 +00:00
}
2020-08-18 17:21:22 +00:00
/**
2020-09-10 19:59:26 +00:00
* Configure the loggers used for the console.
2020-08-18 17:21:22 +00:00
*
2020-09-10 19:59:26 +00:00
* @return void
2020-08-18 17:21:22 +00:00
*/
2020-09-10 19:59:26 +00:00
protected function configureLoggers()
2018-11-27 20:30:33 +00:00
{
2020-09-10 19:59:26 +00:00
$this->configureHttpLogger();
$this->configureMessageLogger();
$this->configureConnectionLogger();
2018-11-27 20:30:33 +00:00
}
2020-08-18 17:21:22 +00:00
/**
2020-09-10 19:59:26 +00:00
* Register the managers that are not resolved
* in the package service provider.
2020-08-18 17:21:22 +00:00
*
2020-09-10 19:59:26 +00:00
* @return void
2020-08-18 17:21:22 +00:00
*/
2020-09-10 19:59:26 +00:00
protected function configureManagers()
2018-11-24 00:25:40 +00:00
{
$this->laravel->singleton(ChannelManager::class, function ($app) {
$config = $app['config']['websockets'];
$mode = $config['replication']['mode'] ?? 'local';
2020-09-10 19:59:26 +00:00
$class = $config['replication']['modes'][$mode]['channel_manager'];
2018-11-24 14:23:59 +00:00
2020-09-10 19:59:26 +00:00
return new $class($this->loop);
});
2018-11-24 14:23:59 +00:00
}
2020-08-18 17:21:22 +00:00
/**
2020-09-10 19:59:26 +00:00
* Register the Statistics Collectors that
* are not resolved in the package service provider.
2020-08-18 17:21:22 +00:00
*
2020-09-10 19:59:26 +00:00
* @return void
2020-08-18 17:21:22 +00:00
*/
2020-09-10 19:59:26 +00:00
protected function configureStatistics()
2018-11-24 14:23:59 +00:00
{
2020-09-10 19:59:26 +00:00
if (! $this->option('disable-statistics')) {
$intervalInSeconds = $this->option('statistics-interval') ?: config('websockets.statistics.interval_in_seconds', 3600);
$this->loop->addPeriodicTimer($intervalInSeconds, function () {
$this->line('Saving statistics...');
StatisticsCollectorFacade::save();
});
}
2018-11-24 00:25:40 +00:00
}
2020-08-18 17:21:22 +00:00
/**
2020-09-10 19:59:26 +00:00
* Configure the restart timer.
2020-08-18 17:21:22 +00:00
*
2020-09-10 19:59:26 +00:00
* @return void
2020-08-18 17:21:22 +00:00
*/
public function configureRestartTimer()
{
$this->lastRestart = $this->getLastRestart();
$this->loop->addPeriodicTimer(10, function () {
if ($this->getLastRestart() !== $this->lastRestart) {
2020-09-13 06:37:31 +00:00
$this->triggerSoftShutdown();
}
});
}
2020-09-11 05:29:48 +00:00
/**
* Register the routes for the server.
*
* @return void
*/
protected function configureRoutes()
{
2021-04-06 14:21:15 +00:00
WebSocketRouter::registerRoutes();
2020-09-11 05:29:48 +00:00
}
/**
* Configure the PCNTL signals for soft shutdown.
*
* @return void
*/
protected function configurePcntlSignal()
{
// When the process receives a SIGTERM or a SIGINT
// signal, it should mark the server as unavailable
// to receive new connections, close the current connections,
// then stopping the loop.
if (! extension_loaded('pcntl')) {
return;
}
$this->loop->addSignal(SIGTERM, function () {
$this->line('Closing existing connections...');
$this->triggerSoftShutdown();
});
$this->loop->addSignal(SIGINT, function () {
$this->line('Closing existing connections...');
$this->triggerSoftShutdown();
});
}
2020-09-15 09:30:17 +00:00
/**
* Configure the tracker that will delete
2020-09-15 09:30:43 +00:00
* from the store the connections that.
2020-09-15 09:30:17 +00:00
*
* @return void
*/
protected function configurePongTracker()
{
$this->loop->addPeriodicTimer(10, function () {
$this->laravel
->make(ChannelManager::class)
->removeObsoleteConnections();
});
}
2020-08-14 12:35:36 +00:00
/**
2020-09-10 19:59:26 +00:00
* Configure the HTTP logger class.
2020-08-14 12:35:36 +00:00
*
* @return void
*/
2020-09-10 19:59:26 +00:00
protected function configureHttpLogger()
2020-08-14 12:35:36 +00:00
{
$this->laravel->singleton(HttpLogger::class, function ($app) {
2020-09-10 19:59:26 +00:00
return (new HttpLogger($this->output))
->enable($this->option('debug') ?: ($app['config']['app']['debug'] ?? false))
2020-09-10 19:59:26 +00:00
->verbose($this->output->isVerbose());
});
2018-11-22 07:49:26 +00:00
}
2020-08-18 17:21:22 +00:00
/**
2020-09-10 19:59:26 +00:00
* Configure the logger for messages.
2020-08-18 17:21:22 +00:00
*
2020-09-10 19:59:26 +00:00
* @return void
2020-08-18 17:21:22 +00:00
*/
2020-09-10 19:59:26 +00:00
protected function configureMessageLogger()
{
$this->laravel->singleton(WebSocketsLogger::class, function ($app) {
2020-09-10 19:59:26 +00:00
return (new WebSocketsLogger($this->output))
->enable($this->option('debug') ?: ($app['config']['app']['debug'] ?? false))
2020-09-10 19:59:26 +00:00
->verbose($this->output->isVerbose());
});
}
2020-09-10 19:59:26 +00:00
/**
* Configure the connection logger.
*
* @return void
*/
protected function configureConnectionLogger()
{
$this->laravel->bind(ConnectionLogger::class, function ($app) {
2020-09-10 19:59:26 +00:00
return (new ConnectionLogger($this->output))
->enable($app['config']['app']['debug'] ?? false)
2020-09-10 19:59:26 +00:00
->verbose($this->output->isVerbose());
});
}
2020-08-18 17:21:22 +00:00
/**
* Start the server.
*
* @return void
*/
2020-09-10 19:59:26 +00:00
protected function startServer()
2018-11-22 07:49:26 +00:00
{
2018-11-24 00:35:08 +00:00
$this->info("Starting the WebSocket server on port {$this->option('port')}...");
2018-11-20 10:51:00 +00:00
2020-08-18 17:21:22 +00:00
$this->buildServer();
2020-08-18 20:09:12 +00:00
$this->server->run();
2018-11-20 10:51:00 +00:00
}
2020-08-18 17:21:22 +00:00
/**
* Build the server instance.
*
* @return void
*/
protected function buildServer()
{
2020-09-10 19:59:26 +00:00
$this->server = new ServerFactory(
2020-08-18 17:21:22 +00:00
$this->option('host'), $this->option('port')
);
if ($loop = $this->option('loop')) {
$this->loop = $loop;
}
2020-08-18 17:21:22 +00:00
$this->server = $this->server
->setLoop($this->loop)
2020-09-10 20:16:17 +00:00
->withRoutes(WebSocketRouter::getRoutes())
2020-08-18 17:21:22 +00:00
->setConsoleOutput($this->output)
->createServer();
}
2020-08-18 17:21:22 +00:00
/**
* Get the last time the server restarted.
*
* @return int
*/
protected function getLastRestart()
{
2020-09-10 19:59:26 +00:00
return Cache::get(
'beyondcode:websockets:restart', 0
);
}
/**
* Trigger a soft shutdown for the process.
*
* @return void
*/
protected function triggerSoftShutdown()
{
$channelManager = $this->laravel->make(ChannelManager::class);
// Close the new connections allowance on this server.
$channelManager->declineNewConnections();
// Get all local connections and close them. They will
// be automatically be unsubscribed from all channels.
$channelManager->getLocalConnections()
->then(function ($connections) {
return all(collect($connections)->map(function ($connection) {
return app('websockets.handler')
->onClose($connection)
->then(function () use ($connection) {
$connection->close();
});
})->toArray());
})
->then(function () {
$this->loop->stop();
});
}
2018-11-24 00:25:40 +00:00
}