Add the ability to limit the maximum concurrent connections per appli… (#143)

* Add the ability to limit the maximum concurrent connections per application

Co-Authored-By: AlShahawi <alshahawi@outlook.com>
This commit is contained in:
Ahmed Al-Shahawi 2019-05-11 08:48:33 +02:00 committed by Marcel Pociot
parent 556f4338e8
commit aecbfeebe7
7 changed files with 59 additions and 1 deletions

View File

@ -8,6 +8,9 @@ return [
* This package comes with multi tenancy out of the box. Here you can * This package comes with multi tenancy out of the box. Here you can
* configure the different apps that can use the webSockets server. * 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 * Optionally you can disable client events so clients cannot send
* messages to each other via the webSockets. * messages to each other via the webSockets.
*/ */
@ -17,6 +20,7 @@ return [
'name' => env('APP_NAME'), 'name' => env('APP_NAME'),
'key' => env('PUSHER_APP_KEY'), 'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'), 'secret' => env('PUSHER_APP_SECRET'),
'capacity' => null,
'enable_client_messages' => false, 'enable_client_messages' => false,
'enable_statistics' => true, 'enable_statistics' => true,
], ],

View File

@ -21,6 +21,9 @@ class App
/** @var string|null */ /** @var string|null */
public $host; public $host;
/** @var int|null */
public $capacity = null;
/** @var bool */ /** @var bool */
public $clientMessagesEnabled = false; public $clientMessagesEnabled = false;
@ -80,6 +83,13 @@ class App
return $this; return $this;
} }
public function setCapacity(?int $capacity)
{
$this->capacity = $capacity;
return $this;
}
public function enableStatistics(bool $enabled = true) public function enableStatistics(bool $enabled = true)
{ {
$this->statisticsEnabled = $enabled; $this->statisticsEnabled = $enabled;

View File

@ -73,7 +73,8 @@ class ConfigAppProvider implements AppProvider
$app $app
->enableClientMessages($appAttributes['enable_client_messages']) ->enableClientMessages($appAttributes['enable_client_messages'])
->enableStatistics($appAttributes['enable_statistics']); ->enableStatistics($appAttributes['enable_statistics'])
->setCapacity($appAttributes['capacity'] ?? null);
return $app; return $app;
} }

View File

@ -0,0 +1,16 @@
<?php
namespace BeyondCode\LaravelWebSockets\WebSockets\Exceptions;
class ConnectionsOverCapacity extends WebSocketException
{
public function __construct()
{
$this->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;
}
}

View File

@ -14,6 +14,7 @@ use BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager;
use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\UnknownAppKey; use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\UnknownAppKey;
use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\WebSocketException; use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\WebSocketException;
use BeyondCode\LaravelWebSockets\WebSockets\Messages\PusherMessageFactory; use BeyondCode\LaravelWebSockets\WebSockets\Messages\PusherMessageFactory;
use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\ConnectionsOverCapacity;
class WebSocketHandler implements MessageComponentInterface class WebSocketHandler implements MessageComponentInterface
{ {
@ -29,6 +30,7 @@ class WebSocketHandler implements MessageComponentInterface
{ {
$this $this
->verifyAppKey($connection) ->verifyAppKey($connection)
->limitConcurrentConnections($connection)
->generateSocketId($connection) ->generateSocketId($connection)
->establishConnection($connection); ->establishConnection($connection);
} }
@ -73,6 +75,18 @@ class WebSocketHandler implements MessageComponentInterface
return $this; 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) protected function generateSocketId(ConnectionInterface $connection)
{ {
$socketId = sprintf('%d.%d', random_int(1, 1000000000), random_int(1, 1000000000)); $socketId = sprintf('%d.%d', random_int(1, 1000000000), random_int(1, 1000000000));

View File

@ -5,6 +5,7 @@ namespace BeyondCode\LaravelWebSockets\Tests;
use BeyondCode\LaravelWebSockets\Apps\App; use BeyondCode\LaravelWebSockets\Apps\App;
use BeyondCode\LaravelWebSockets\Tests\Mocks\Message; use BeyondCode\LaravelWebSockets\Tests\Mocks\Message;
use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\UnknownAppKey; use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\UnknownAppKey;
use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\ConnectionsOverCapacity;
class ConnectionTest extends TestCase class ConnectionTest extends TestCase
{ {
@ -26,6 +27,17 @@ class ConnectionTest extends TestCase
$connection->assertSentEvent('pusher:connection_established'); $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 */ /** @test */
public function successful_connections_have_the_app_attached() public function successful_connections_have_the_app_attached()
{ {

View File

@ -49,6 +49,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
'id' => 1234, 'id' => 1234,
'key' => 'TestKey', 'key' => 'TestKey',
'secret' => 'TestSecret', 'secret' => 'TestSecret',
'capacity' => null,
'enable_client_messages' => false, 'enable_client_messages' => false,
'enable_statistics' => true, 'enable_statistics' => true,
], ],