diff options
author | Joas Schilling <coding@schilljs.com> | 2020-04-07 17:27:36 +0300 |
---|---|---|
committer | Joas Schilling <coding@schilljs.com> | 2020-05-12 11:40:19 +0300 |
commit | 6ddd276dd0bc8ef91cf1724363f3f33ec38fa8f0 (patch) | |
tree | 406e7410c1570acf80f93aece4b4a71b6631d617 /lib | |
parent | 6c3b4836c49aeab07cd7c84dc25c6756e1b4d2e3 (diff) |
Extend the signaling setting API to allow somewhat clustering
Signed-off-by: Joas Schilling <coding@schilljs.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Config.php | 43 | ||||
-rw-r--r-- | lib/Controller/SignalingController.php | 107 | ||||
-rw-r--r-- | lib/Manager.php | 6 | ||||
-rw-r--r-- | lib/Migration/Version8000Date20200407115318.php | 39 | ||||
-rw-r--r-- | lib/Room.php | 21 |
5 files changed, 160 insertions, 56 deletions
diff --git a/lib/Config.php b/lib/Config.php index 0388e1028..3e63719a8 100644 --- a/lib/Config.php +++ b/lib/Config.php @@ -94,49 +94,6 @@ class Config { return $this->config->getUserValue($userId, 'spreed', 'attachment_folder', '/Talk'); } - public function getSettings(?string $userId): array { - $stun = []; - $stunServer = $this->getStunServer(); - if ($stunServer) { - $stun[] = [ - 'url' => 'stun:' . $stunServer, - ]; - } - $turn = []; - $turnSettings = $this->getTurnSettings(); - if (!empty($turnSettings['server'])) { - $protocols = explode(',', $turnSettings['protocols']); - foreach ($protocols as $proto) { - $turn[] = [ - 'url' => ['turn:' . $turnSettings['server'] . '?transport=' . $proto], - 'urls' => ['turn:' . $turnSettings['server'] . '?transport=' . $proto], - 'username' => $turnSettings['username'], - 'credential' => $turnSettings['password'], - ]; - } - } - - $signaling = []; - $servers = $this->getSignalingServers(); - if (!empty($servers)) { - try { - $signaling = $servers[random_int(0, count($servers) - 1)]; - } catch (\Exception $e) { - $signaling = $servers[0]; - } - $signaling = $signaling['server']; - } - - return [ - 'userId' => $userId, - 'hideWarning' => !empty($signaling) || $this->getHideSignalingWarning(), - 'server' => $signaling, - 'ticket' => $this->getSignalingTicket($userId), - 'stunservers' => $stun, - 'turnservers' => $turn, - ]; - } - /** * @return string[] */ diff --git a/lib/Controller/SignalingController.php b/lib/Controller/SignalingController.php index b10eaed64..09e9e536b 100644 --- a/lib/Controller/SignalingController.php +++ b/lib/Controller/SignalingController.php @@ -39,6 +39,9 @@ use OCP\AppFramework\OCSController; use OCP\AppFramework\Utility\ITimeFactory; use OCP\EventDispatcher\IEventDispatcher; use OCP\Http\Client\IClientService; +use OCP\ICache; +use OCP\ICacheFactory; +use OCP\IConfig; use OCP\IDBConnection; use OCP\IRequest; use OCP\IUser; @@ -52,7 +55,11 @@ class SignalingController extends OCSController { public const EVENT_BACKEND_SIGNALING_ROOMS = self::class . '::signalingBackendRoom'; /** @var Config */ - private $config; + private $talkConfig; + /** @var IConfig */ + private $serverConfig; + /** @var ICache */ + private $cache; /** @var TalkSession */ private $session; /** @var Manager */ @@ -74,7 +81,9 @@ class SignalingController extends OCSController { public function __construct(string $appName, IRequest $request, - Config $config, + Config $talkConfig, + IConfig $serverConfig, + ICacheFactory $cacheFactory, TalkSession $session, Manager $manager, IDBConnection $connection, @@ -85,7 +94,9 @@ class SignalingController extends OCSController { IClientService $clientService, ?string $UserId) { parent::__construct($appName, $request); - $this->config = $config; + $this->talkConfig = $talkConfig; + $this->serverConfig = $serverConfig; + $this->cache = $cacheFactory->createDistributed('hpb_servers'); $this->session = $session; $this->dbConnection = $connection; $this->manager = $manager; @@ -100,13 +111,83 @@ class SignalingController extends OCSController { /** * @PublicPage * - * Only available for logged in users because guests can not use the apps - * right now. - * + * @param string $token * @return DataResponse */ - public function getSettings(): DataResponse { - return new DataResponse($this->config->getSettings($this->userId)); + public function getSettings(string $token = ''): DataResponse { + $stun = []; + $stunServer = $this->talkConfig->getStunServer(); + if ($stunServer) { + $stun[] = [ + 'url' => 'stun:' . $stunServer, + ]; + } + + $turn = []; + $turnSettings = $this->talkConfig->getTurnSettings(); + if (!empty($turnSettings['server'])) { + $protocols = explode(',', $turnSettings['protocols']); + foreach ($protocols as $proto) { + $turn[] = [ + 'url' => ['turn:' . $turnSettings['server'] . '?transport=' . $proto], + 'urls' => ['turn:' . $turnSettings['server'] . '?transport=' . $proto], + 'username' => $turnSettings['username'], + 'credential' => $turnSettings['password'], + ]; + } + } + + $signaling = ''; + $servers = $this->talkConfig->getSignalingServers(); + if (!empty($servers)) { + try { + $serverId = random_int(0, count($servers) - 1); + } catch (\Exception $e) { + $serverId = 0; + } + $signalingClusterMode = $this->serverConfig->getAppValue('spreed', 'hpb_cluster_mode', ''); + if ($signalingClusterMode === 'conversation') { + try { + $serverId = $this->getSignalingServerForConversation($this->userId, $serverId, $token); + } catch (RoomNotFoundException $e) { + return new DataResponse([], Http::STATUS_NOT_FOUND); + } + } + $signaling = $servers[$serverId]['server']; + } + + return new DataResponse([ + 'userId' => $this->userId, + 'hideWarning' => $signaling !== '' || $this->talkConfig->getHideSignalingWarning(), + 'server' => $signaling, + 'ticket' => $this->talkConfig->getSignalingTicket($this->userId), + 'stunservers' => $stun, + 'turnservers' => $turn, + ]); + } + + /** + * @param string|null $userId + * @param int $randomServerId + * @param string $token + * @throws RoomNotFoundException + * @return int + */ + protected function getSignalingServerForConversation(?string $userId, int $randomServerId, string $token): int { + $room = $this->manager->getRoomForParticipantByToken($token, $userId); + $serverId = $room->getAssignedSignalingServer(); + + if ($serverId === null) { + // Avoid concurrency issues + $serverId = $this->cache->get($token); + if ($serverId === null) { + $this->cache->set($token, $randomServerId); + $serverId = $randomServerId; + $room->setAssignedSignalingServer($randomServerId); + } + } + + return $serverId; } /** @@ -117,7 +198,7 @@ class SignalingController extends OCSController { * @return DataResponse */ public function getWelcomeMessage(int $serverId): DataResponse { - $signalingServers = $this->config->getSignalingServers(); + $signalingServers = $this->talkConfig->getSignalingServers(); if (empty($signalingServers) || !isset($signalingServers[$serverId])) { return new DataResponse([], Http::STATUS_NOT_FOUND); } @@ -161,7 +242,7 @@ class SignalingController extends OCSController { * @return DataResponse */ public function signaling(string $token, string $messages): DataResponse { - $signaling = $this->config->getSignalingServers(); + $signaling = $this->talkConfig->getSignalingServers(); if (!empty($signaling)) { return new DataResponse('Internal signaling disabled.', Http::STATUS_BAD_REQUEST); } @@ -207,7 +288,7 @@ class SignalingController extends OCSController { * @return DataResponse */ public function pullMessages(string $token): DataResponse { - $signaling = $this->config->getSignalingServers(); + $signaling = $this->talkConfig->getSignalingServers(); if (!empty($signaling)) { return new DataResponse('Internal signaling disabled.', Http::STATUS_BAD_REQUEST); } @@ -332,7 +413,7 @@ class SignalingController extends OCSController { if (empty($checksum)) { return false; } - $hash = hash_hmac('sha256', $random . $data, $this->config->getSignalingSecret()); + $hash = hash_hmac('sha256', $random . $data, $this->talkConfig->getSignalingSecret()); return hash_equals($hash, strtolower($checksum)); } @@ -394,7 +475,7 @@ class SignalingController extends OCSController { private function backendAuth(array $auth): DataResponse { $params = $auth['params']; $userId = $params['userid']; - if (!$this->config->validateSignalingTicket($userId, $params['ticket'])) { + if (!$this->talkConfig->validateSignalingTicket($userId, $params['ticket'])) { return new DataResponse([ 'type' => 'error', 'error' => [ diff --git a/lib/Manager.php b/lib/Manager.php index 002afb624..f4c8bfde7 100644 --- a/lib/Manager.php +++ b/lib/Manager.php @@ -140,6 +140,11 @@ class Manager { ])); } + $assignedSignalingServer = $row['assigned_hpb']; + if ($assignedSignalingServer !== null) { + $assignedSignalingServer = (int) $assignedSignalingServer; + } + return new Room( $this, $this->db, @@ -151,6 +156,7 @@ class Manager { (int) $row['type'], (int) $row['read_only'], (int) $row['lobby_state'], + $assignedSignalingServer, (string) $row['token'], (string) $row['name'], (string) $row['password'], diff --git a/lib/Migration/Version8000Date20200407115318.php b/lib/Migration/Version8000Date20200407115318.php new file mode 100644 index 000000000..7764fd7c0 --- /dev/null +++ b/lib/Migration/Version8000Date20200407115318.php @@ -0,0 +1,39 @@ +<?php + +declare(strict_types=1); + +namespace OCA\Talk\Migration; + +use Closure; +use Doctrine\DBAL\Types\Type; +use OCP\DB\ISchemaWrapper; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + +/** + * Auto-generated migration step: Please modify to your needs! + */ +class Version8000Date20200407115318 extends SimpleMigrationStep { + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + $table = $schema->getTable('talk_rooms'); + if (!$table->hasColumn('assigned_hpb')) { + $table->addColumn('assigned_hpb', Type::INTEGER, [ + 'notnull' => false, + 'length' => 4, + 'unsigned' => false, + 'default' => null, + ]); + } + + return $schema; + } +} diff --git a/lib/Room.php b/lib/Room.php index 3e7d2fdd3..9ab23c51e 100644 --- a/lib/Room.php +++ b/lib/Room.php @@ -124,6 +124,8 @@ class Room { private $readOnly; /** @var int */ private $lobbyState; + /** @var int|null */ + private $assignedSignalingServer; /** @var \DateTime|null */ private $lobbyTimer; /** @var string */ @@ -162,6 +164,7 @@ class Room { int $type, int $readOnly, int $lobbyState, + ?int $assignedSignalingServer, string $token, string $name, string $password, @@ -183,6 +186,7 @@ class Room { $this->type = $type; $this->readOnly = $readOnly; $this->lobbyState = $lobbyState; + $this->assignedSignalingServer = $assignedSignalingServer; $this->token = $token; $this->name = $name; $this->password = $password; @@ -224,6 +228,10 @@ class Room { } } + public function getAssignedSignalingServer(): ?int { + return $this->assignedSignalingServer; + } + public function getToken(): string { return $this->token; } @@ -501,6 +509,19 @@ class Room { return (bool) $query->execute(); } + public function setAssignedSignalingServer(?int $signalingServer): bool { + $query = $this->db->getQueryBuilder(); + $query->update('talk_rooms') + ->set('assigned_hpb', $query->createNamedParameter($signalingServer)) + ->where($query->expr()->eq('id', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT))); + + if ($signalingServer !== null) { + $query->andWhere($query->expr()->isNull('assigned_hpb')); + } + + return (bool) $query->execute(); + } + /** * @param int $newType Currently it is only allowed to change between `self::GROUP_CALL` and `self::PUBLIC_CALL` * @return bool True when the change was valid, false otherwise |