loop = LoopFactory::create(); $this->router = new RouteGenerator(); } public function setHost(string $host) { $this->host = $host; return $this; } public function setPort(int $port) { $this->port = $port; return $this; } public function setLoop(LoopInterface $loop) { $this->loop = $loop; return $this; } public function setHostname(string $hostname) { $this->hostname = $hostname; return $this; } protected function addTunnelRoute() { $this->router->addSymfonyRoute('tunnel', new Route('/{__catchall__}', [ '_controller' => app(TunnelMessageController::class), ], [ '__catchall__' => '.*', ])); } protected function addControlConnectionRoute(): WsServer { $wsServer = new WsServer(app(ControlMessageController::class)); $this->router->addSymfonyRoute('expose-control', new Route('/expose/control', [ '_controller' => $wsServer, ], [], [], '', [], [], 'request.headers.get("x-expose-control") matches "/enabled/i"')); return $wsServer; } protected function addAdminRoutes() { $adminCondition = 'request.headers.get("Host") matches "/^'.config('expose.admin.subdomain').'\\\\./i"'; $this->router->get('/', RedirectToUsersController::class, $adminCondition); $this->router->get('/users', ListUsersController::class, $adminCondition); $this->router->get('/settings', ShowSettingsController::class, $adminCondition); $this->router->get('/sites', ListSitesController::class, $adminCondition); $this->router->get('/tcp', ListTcpConnectionsController::class, $adminCondition); $this->router->get('/api/statistics', GetStatisticsController::class, $adminCondition); $this->router->get('/api/settings', GetSettingsController::class, $adminCondition); $this->router->post('/api/settings', StoreSettingsController::class, $adminCondition); $this->router->get('/api/users', GetUsersController::class, $adminCondition); $this->router->post('/api/users', StoreUsersController::class, $adminCondition); $this->router->get('/api/users/{id}', GetUserDetailsController::class, $adminCondition); $this->router->get('/api/logs', GetLogsController::class, $adminCondition); $this->router->get('/api/logs/{subdomain}', GetLogsForSubdomainController::class, $adminCondition); $this->router->post('/api/domains', StoreDomainController::class, $adminCondition); $this->router->delete('/api/domains/{domain}', DeleteSubdomainController::class, $adminCondition); $this->router->post('/api/subdomains', StoreSubdomainController::class, $adminCondition); $this->router->delete('/api/subdomains/{subdomain}', DeleteSubdomainController::class, $adminCondition); $this->router->delete('/api/users/{id}', DeleteUsersController::class, $adminCondition); $this->router->get('/api/sites', GetSitesController::class, $adminCondition); $this->router->get('/api/sites/{site}', GetSiteDetailsController::class, $adminCondition); $this->router->delete('/api/sites/{id}', DisconnectSiteController::class, $adminCondition); $this->router->get('/api/tcp', GetTcpConnectionsController::class, $adminCondition); $this->router->delete('/api/tcp/{id}', DisconnectTcpConnectionController::class, $adminCondition); } protected function bindConfiguration() { app()->singleton(Configuration::class, function ($app) { return new Configuration($this->hostname, $this->port); }); return $this; } protected function bindSubdomainGenerator() { app()->singleton(SubdomainGenerator::class, function ($app) { return $app->make(config('expose.admin.subdomain_generator')); }); return $this; } protected function bindConnectionManager() { app()->singleton(ConnectionManagerContract::class, function ($app) { return $app->make(ConnectionManager::class); }); return $this; } public function createServer() { $this->socket = new Server("{$this->host}:{$this->port}", $this->loop); $this->bindConfiguration() ->bindSubdomainGenerator() ->bindUserRepository() ->bindLoggerRepository() ->bindSubdomainRepository() ->bindDomainRepository() ->bindDatabase() ->ensureDatabaseIsInitialized() ->registerStatisticsCollector() ->bindConnectionManager() ->addAdminRoutes(); $controlConnection = $this->addControlConnectionRoute(); $this->addTunnelRoute(); $urlMatcher = new UrlMatcher($this->router->getRoutes(), new RequestContext); $router = new Router($urlMatcher); $http = new HttpServer($router); $server = new IoServer($http, $this->socket, $this->loop); $controlConnection->enableKeepAlive($this->loop); return $server; } public function getSocket(): Server { return $this->socket; } protected function bindUserRepository() { app()->singleton(UserRepository::class, function () { return app(config('expose.admin.user_repository')); }); return $this; } protected function bindSubdomainRepository() { app()->singleton(SubdomainRepository::class, function () { return app(config('expose.admin.subdomain_repository', DatabaseSubdomainRepository::class)); }); return $this; } protected function bindLoggerRepository() { app()->singleton(LoggerRepository::class, function () { return app(config('expose.admin.logger_repository', NullLogger::class)); }); return $this; } protected function bindDomainRepository() { app()->singleton(DomainRepository::class, function () { return app(config('expose.admin.domain_repository', DatabaseDomainRepository::class)); }); return $this; } protected function bindDatabase() { app()->singleton(DatabaseInterface::class, function () { $factory = new \Clue\React\SQLite\Factory($this->loop); $options = ['worker_command' => Phar::running(false) ? Phar::running(false).' --sqlite-worker' : null]; return $factory->openLazy( config('expose.admin.database', ':memory:'), null, $options, ); }); return $this; } protected function ensureDatabaseIsInitialized() { /** @var DatabaseInterface $db */ $db = app(DatabaseInterface::class); $migrations = (new Finder()) ->files() ->ignoreDotFiles(true) ->in(database_path('migrations')) ->name('*.sql') ->sortByName(); /** @var SplFileInfo $migration */ foreach ($migrations as $migration) { $db->exec($migration->getContents()); } return $this; } public function validateAuthTokens(bool $validate) { config()->set('expose.admin.validate_auth_tokens', $validate); return $this; } protected function registerStatisticsCollector() { if (config('expose.admin.statistics.enable_statistics', true) === false) { return; } app()->singleton(StatisticsRepository::class, function () { return app(config('expose.admin.statistics.repository', DatabaseStatisticsRepository::class)); }); app()->singleton(StatisticsCollector::class, function () { return app(DatabaseStatisticsCollector::class); }); $intervalInSeconds = config('expose.admin.statistics.interval_in_seconds', 3600); $this->loop->addPeriodicTimer($intervalInSeconds, function () { app(StatisticsCollector::class)->save(); }); return $this; } }