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;
|
2022-10-06 11:46:54 +00:00
|
|
|
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
|
|
|
|
|
*/
|
2020-08-13 11:21:06 +00:00
|
|
|
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.}
|
2020-08-13 11:21:06 +00:00
|
|
|
{--debug : Forces the loggers to be enabled and thereby overriding the APP_DEBUG setting.}
|
2020-09-12 14:45:07 +00:00
|
|
|
{--loop : Programatically inject the loop.}
|
2020-08-13 11:21:06 +00:00
|
|
|
';
|
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()
|
|
|
|
|
{
|
2022-10-06 11:46:54 +00:00
|
|
|
$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-08-19 06:00:53 +00:00
|
|
|
|
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();
|
|
|
|
|
|
2020-09-12 14:45:07 +00:00
|
|
|
$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
|
|
|
{
|
2021-04-06 13:47:54 +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
|
|
|
|
2021-04-06 13:47:54 +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
|
|
|
*/
|
2019-11-06 08:25:55 +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();
|
2019-11-06 08:25:55 +00:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2020-09-12 14:45:07 +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.
|
|
|
|
|
|
2021-01-08 08:27:11 +00:00
|
|
|
if (! extension_loaded('pcntl')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-12 14:45:07 +00:00
|
|
|
$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
|
|
|
{
|
2021-04-06 13:47:54 +00:00
|
|
|
$this->laravel->singleton(HttpLogger::class, function ($app) {
|
2020-09-10 19:59:26 +00:00
|
|
|
return (new HttpLogger($this->output))
|
2021-04-06 13:47:54 +00:00
|
|
|
->enable($this->option('debug') ?: ($app['config']['app']['debug'] ?? false))
|
2020-09-10 19:59:26 +00:00
|
|
|
->verbose($this->output->isVerbose());
|
2020-08-28 16:44:54 +00:00
|
|
|
});
|
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()
|
2019-05-11 06:46:25 +00:00
|
|
|
{
|
2021-04-06 13:47:54 +00:00
|
|
|
$this->laravel->singleton(WebSocketsLogger::class, function ($app) {
|
2020-09-10 19:59:26 +00:00
|
|
|
return (new WebSocketsLogger($this->output))
|
2021-04-06 13:47:54 +00:00
|
|
|
->enable($this->option('debug') ?: ($app['config']['app']['debug'] ?? false))
|
2020-09-10 19:59:26 +00:00
|
|
|
->verbose($this->output->isVerbose());
|
|
|
|
|
});
|
|
|
|
|
}
|
2019-07-28 19:29:16 +00:00
|
|
|
|
2020-09-10 19:59:26 +00:00
|
|
|
/**
|
|
|
|
|
* Configure the connection logger.
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
protected function configureConnectionLogger()
|
|
|
|
|
{
|
2021-04-06 13:47:54 +00:00
|
|
|
$this->laravel->bind(ConnectionLogger::class, function ($app) {
|
2020-09-10 19:59:26 +00:00
|
|
|
return (new ConnectionLogger($this->output))
|
2021-04-06 13:47:54 +00:00
|
|
|
->enable($app['config']['app']['debug'] ?? false)
|
2020-09-10 19:59:26 +00:00
|
|
|
->verbose($this->output->isVerbose());
|
|
|
|
|
});
|
2019-05-11 06:46:25 +00:00
|
|
|
}
|
|
|
|
|
|
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-13 11:21:06 +00:00
|
|
|
|
2020-08-18 20:09:12 +00:00
|
|
|
$this->server->run();
|
2018-11-20 10:51:00 +00:00
|
|
|
}
|
2018-12-10 09:31:19 +00:00
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Build the server instance.
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
protected function buildServer()
|
2018-12-17 12:12:53 +00:00
|
|
|
{
|
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')
|
|
|
|
|
);
|
2018-12-17 12:12:53 +00:00
|
|
|
|
2020-09-12 14:45:07 +00:00
|
|
|
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();
|
2018-12-17 12:12:53 +00:00
|
|
|
}
|
|
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Get the last time the server restarted.
|
|
|
|
|
*
|
|
|
|
|
* @return int
|
|
|
|
|
*/
|
2019-11-06 08:25:55 +00:00
|
|
|
protected function getLastRestart()
|
|
|
|
|
{
|
2020-09-10 19:59:26 +00:00
|
|
|
return Cache::get(
|
|
|
|
|
'beyondcode:websockets:restart', 0
|
|
|
|
|
);
|
2019-11-06 08:25:55 +00:00
|
|
|
}
|
2020-09-12 14:45:07 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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.
|
2020-12-07 21:30:36 +00:00
|
|
|
$channelManager->getLocalConnections()
|
|
|
|
|
->then(function ($connections) {
|
2022-10-06 11:46:54 +00:00
|
|
|
return all(collect($connections)->map(function ($connection) {
|
|
|
|
|
return app('websockets.handler')
|
|
|
|
|
->onClose($connection)
|
|
|
|
|
->then(function () use ($connection) {
|
|
|
|
|
$connection->close();
|
|
|
|
|
});
|
|
|
|
|
})->toArray());
|
2020-12-07 21:30:36 +00:00
|
|
|
})
|
|
|
|
|
->then(function () {
|
|
|
|
|
$this->loop->stop();
|
|
|
|
|
});
|
2020-09-12 14:45:07 +00:00
|
|
|
}
|
2018-11-24 00:25:40 +00:00
|
|
|
}
|