diff --git a/config/websockets.php b/config/websockets.php index 74075b5..f453833 100644 --- a/config/websockets.php +++ b/config/websockets.php @@ -8,6 +8,9 @@ return [ * This package comes with multi tenancy out of the box. Here you can * configure the different apps that can use the webSockets server. * + * Optionally you specify capacity so you can limit the maximum + * concurrent connections for a specific app. + * * Optionally you can disable client events so clients cannot send * messages to each other via the webSockets. */ @@ -17,6 +20,7 @@ return [ 'name' => env('APP_NAME'), 'key' => env('PUSHER_APP_KEY'), 'secret' => env('PUSHER_APP_SECRET'), + 'capacity' => null, 'enable_client_messages' => false, 'enable_statistics' => true, ], diff --git a/src/Apps/App.php b/src/Apps/App.php index b11023d..9544725 100644 --- a/src/Apps/App.php +++ b/src/Apps/App.php @@ -21,6 +21,9 @@ class App /** @var string|null */ public $host; + /** @var int|null */ + public $capacity = null; + /** @var bool */ public $clientMessagesEnabled = false; @@ -80,6 +83,13 @@ class App return $this; } + public function setCapacity(?int $capacity) + { + $this->capacity = $capacity; + + return $this; + } + public function enableStatistics(bool $enabled = true) { $this->statisticsEnabled = $enabled; diff --git a/src/Apps/ConfigAppProvider.php b/src/Apps/ConfigAppProvider.php index 786c9e2..0476aba 100644 --- a/src/Apps/ConfigAppProvider.php +++ b/src/Apps/ConfigAppProvider.php @@ -73,7 +73,8 @@ class ConfigAppProvider implements AppProvider $app ->enableClientMessages($appAttributes['enable_client_messages']) - ->enableStatistics($appAttributes['enable_statistics']); + ->enableStatistics($appAttributes['enable_statistics']) + ->setCapacity($appAttributes['capacity'] ?? null); return $app; } diff --git a/src/WebSockets/Exceptions/ConnectionsOverCapacity.php b/src/WebSockets/Exceptions/ConnectionsOverCapacity.php new file mode 100644 index 0000000..9b0522f --- /dev/null +++ b/src/WebSockets/Exceptions/ConnectionsOverCapacity.php @@ -0,0 +1,16 @@ +message = 'Over capacity'; + + // @See https://pusher.com/docs/pusher_protocol#error-codes + // Indicates an error resulting in the connection + // being closed by Pusher, and that the client may reconnect after 1s or more. + $this->code = 4100; + } +} diff --git a/src/WebSockets/WebSocketHandler.php b/src/WebSockets/WebSocketHandler.php index 20c5b92..a9d0c8c 100644 --- a/src/WebSockets/WebSocketHandler.php +++ b/src/WebSockets/WebSocketHandler.php @@ -14,6 +14,7 @@ use BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager; use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\UnknownAppKey; use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\WebSocketException; use BeyondCode\LaravelWebSockets\WebSockets\Messages\PusherMessageFactory; +use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\ConnectionsOverCapacity; class WebSocketHandler implements MessageComponentInterface { @@ -29,6 +30,7 @@ class WebSocketHandler implements MessageComponentInterface { $this ->verifyAppKey($connection) + ->limitConcurrentConnections($connection) ->generateSocketId($connection) ->establishConnection($connection); } @@ -73,6 +75,18 @@ class WebSocketHandler implements MessageComponentInterface return $this; } + protected function limitConcurrentConnections(ConnectionInterface $connection) + { + if (! is_null($capacity = $connection->app->capacity)) { + $connectionsCount = $this->channelManager->getConnectionCount($connection->app->id); + if ($connectionsCount >= $capacity) { + throw new ConnectionsOverCapacity(); + } + } + + return $this; + } + protected function generateSocketId(ConnectionInterface $connection) { $socketId = sprintf('%d.%d', random_int(1, 1000000000), random_int(1, 1000000000)); diff --git a/tests/ConnectionTest.php b/tests/ConnectionTest.php index 62cf154..0c832ad 100644 --- a/tests/ConnectionTest.php +++ b/tests/ConnectionTest.php @@ -5,6 +5,7 @@ namespace BeyondCode\LaravelWebSockets\Tests; use BeyondCode\LaravelWebSockets\Apps\App; use BeyondCode\LaravelWebSockets\Tests\Mocks\Message; use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\UnknownAppKey; +use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\ConnectionsOverCapacity; class ConnectionTest extends TestCase { @@ -26,6 +27,17 @@ class ConnectionTest extends TestCase $connection->assertSentEvent('pusher:connection_established'); } + /** @test */ + public function app_can_not_exceed_maximum_capacity() + { + $this->app['config']->set('websockets.apps.0.capacity', 2); + + $this->getConnectedWebSocketConnection(['test-channel']); + $this->getConnectedWebSocketConnection(['test-channel']); + $this->expectException(ConnectionsOverCapacity::class); + $this->getConnectedWebSocketConnection(['test-channel']); + } + /** @test */ public function successful_connections_have_the_app_attached() { diff --git a/tests/TestCase.php b/tests/TestCase.php index e10f9a5..14d3655 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -49,6 +49,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase 'id' => 1234, 'key' => 'TestKey', 'secret' => 'TestSecret', + 'capacity' => null, 'enable_client_messages' => false, 'enable_statistics' => true, ],