loop = LoopFactory::create(); } /** * Run the command. * * @return void */ public function handle() { $this->configureLoggers(); $this->configureManagers(); $this->configureStatistics(); $this->configureRestartTimer(); $this->configureRoutes(); $this->configurePcntlSignal(); $this->startServer(); } /** * Configure the loggers used for the console. * * @return void */ protected function configureLoggers() { $this->configureHttpLogger(); $this->configureMessageLogger(); $this->configureConnectionLogger(); } /** * Register the managers that are not resolved * in the package service provider. * * @return void */ protected function configureManagers() { $this->laravel->singleton(ChannelManager::class, function () { $mode = config('websockets.replication.mode', 'local'); $class = config("websockets.replication.modes.{$mode}.channel_manager"); return new $class($this->loop); }); } /** * Register the Statistics Collectors that * are not resolved in the package service provider. * * @return void */ protected function configureStatistics() { 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(); }); } } /** * Configure the restart timer. * * @return void */ public function configureRestartTimer() { $this->lastRestart = $this->getLastRestart(); $this->loop->addPeriodicTimer(10, function () { if ($this->getLastRestart() !== $this->lastRestart) { $this->loop->stop(); } }); } /** * Register the routes for the server. * * @return void */ protected function configureRoutes() { WebSocketRouter::routes(); } /** * 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. $this->loop->addSignal(SIGTERM, function () { $this->line('Closing existing connections...'); $this->triggerSoftShutdown(); }); $this->loop->addSignal(SIGINT, function () { $this->line('Closing existing connections...'); $this->triggerSoftShutdown(); }); } /** * Configure the HTTP logger class. * * @return void */ protected function configureHttpLogger() { $this->laravel->singleton(HttpLogger::class, function () { return (new HttpLogger($this->output)) ->enable($this->option('debug') ?: config('app.debug')) ->verbose($this->output->isVerbose()); }); } /** * Configure the logger for messages. * * @return void */ protected function configureMessageLogger() { $this->laravel->singleton(WebSocketsLogger::class, function () { return (new WebSocketsLogger($this->output)) ->enable($this->option('debug') ?: config('app.debug')) ->verbose($this->output->isVerbose()); }); } /** * Configure the connection logger. * * @return void */ protected function configureConnectionLogger() { $this->laravel->bind(ConnectionLogger::class, function () { return (new ConnectionLogger($this->output)) ->enable(config('app.debug')) ->verbose($this->output->isVerbose()); }); } /** * Start the server. * * @return void */ protected function startServer() { $this->info("Starting the WebSocket server on port {$this->option('port')}..."); $this->buildServer(); $this->server->run(); } /** * Build the server instance. * * @return void */ protected function buildServer() { $this->server = new ServerFactory( $this->option('host'), $this->option('port') ); if ($loop = $this->option('loop')) { $this->loop = $loop; } $this->server = $this->server ->setLoop($this->loop) ->withRoutes(WebSocketRouter::getRoutes()) ->setConsoleOutput($this->output) ->createServer(); } /** * Get the last time the server restarted. * * @return int */ protected function getLastRestart() { 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) use ($channelManager) { foreach ($connections as $connection) { $connection->close(); } }) ->then(function () { $this->loop->stop(); }); } }