Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/reverb.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
'allowed_origins' => ['*'],
'ping_interval' => env('REVERB_APP_PING_INTERVAL', 60),
'activity_timeout' => env('REVERB_APP_ACTIVITY_TIMEOUT', 30),
'max_connections' => env('REVERB_APP_MAX_CONNECTIONS'),
'max_message_size' => env('REVERB_APP_MAX_MESSAGE_SIZE', 10_000),
],
],
Expand Down
17 changes: 17 additions & 0 deletions src/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public function __construct(
protected int $activityTimeout,
protected array $allowedOrigins,
protected int $maxMessageSize,
protected ?int $maxConnections = null,
protected array $options = [],
Comment on lines +18 to 19

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this not a BC break? It would have been better to insert after $options... 🤷🏼‍♂️

) {
//
Expand Down Expand Up @@ -70,6 +71,22 @@ public function activityTimeout(): int
return $this->activityTimeout;
}

/**
* Get the maximum connections allowed for the application.
*/
public function maxConnections(): ?int
{
return $this->maxConnections;
}

/**
* Determine if the application has a maximum connection limit.
*/
public function hasMaxConnectionLimit(): bool
{
return $this->maxConnections !== null;
}

/**
* Get the maximum message size allowed from the client.
*/
Expand Down
1 change: 1 addition & 0 deletions src/ConfigApplicationProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public function find(string $key, mixed $value): Application
$app['activity_timeout'] ?? 30,
$app['allowed_origins'],
$app['max_message_size'],
$app['max_connections'] ?? null,
$app['options'] ?? [],
);
}
Expand Down
20 changes: 20 additions & 0 deletions src/Protocols/Pusher/Exceptions/ConnectionLimitExceeded.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Laravel\Reverb\Protocols\Pusher\Exceptions;

class ConnectionLimitExceeded extends PusherException
{
/**
* The error code associated with the exception.
*
* @var int
*/
protected $code = 4004;

/**
* The error message associated with the exception.
*
* @var string
*/
protected $message = 'Application is over connection quota';
}
18 changes: 18 additions & 0 deletions src/Protocols/Pusher/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Laravel\Reverb\Events\MessageReceived;
use Laravel\Reverb\Loggers\Log;
use Laravel\Reverb\Protocols\Pusher\Contracts\ChannelManager;
use Laravel\Reverb\Protocols\Pusher\Exceptions\ConnectionLimitExceeded;
use Laravel\Reverb\Protocols\Pusher\Exceptions\InvalidOrigin;
use Laravel\Reverb\Protocols\Pusher\Exceptions\PusherException;
use Ratchet\RFC6455\Messaging\Frame;
Expand All @@ -31,6 +32,7 @@ public function __construct(protected ChannelManager $channels, protected EventH
public function open(Connection $connection): void
{
try {
$this->ensureWithinConnectionLimit($connection);
$this->verifyOrigin($connection);

$connection->touch();
Expand Down Expand Up @@ -130,6 +132,22 @@ public function error(Connection $connection, Throwable $exception): void
Log::info($exception->getMessage());
}

/**
* Ensure the server is within the connection limit.
*/
protected function ensureWithinConnectionLimit(Connection $connection): void
{
if (! $connection->app()->hasMaxConnectionLimit()) {
return;
}

$connections = $this->channels->for($connection->app())->connections();

if (count($connections) >= $connection->app()->maxConnections()) {
throw new ConnectionLimitExceeded;
}
}

/**
* Verify the origin of the connection.
*
Expand Down
2 changes: 1 addition & 1 deletion tests/FakeApplicationProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class FakeApplicationProvider implements ApplicationProvider
public function __construct()
{
$this->apps = collect([
new Application('id', 'key', 'secret', 60, 30, ['*'], 10_000, [
new Application('id', 'key', 'secret', 60, 30, ['*'], 10_000, options: [
'host' => 'localhost',
'port' => 443,
'scheme' => 'https',
Expand Down
16 changes: 16 additions & 0 deletions tests/Feature/Protocols/Pusher/Reverb/ServerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,22 @@
connect();
});

it('cannot connect when over the max connection limit', function () {
subscribe('test-channel', connection: connect(key: 'reverb-key-2'));

$connection = await(wsConnect('ws://0.0.0.0:8080/app/reverb-key-2'));

$promise = new Deferred;

$connection->on('message', function ($message) use ($promise) {
$promise->resolve((string) $message);
});

$message = await($promise->promise());

expect($message)->toBe('{"event":"pusher:error","data":"{\"code\":4004,\"message\":\"Application is over connection quota\"}"}');
});

it('limits the size of messages', function () {
$connection = connect(key: 'reverb-key-3', headers: ['Origin' => 'http://laravel.com']);
send(['This message is waaaaaay longer than the 1 byte limit'], $connection);
Expand Down
1 change: 1 addition & 0 deletions tests/ReverbTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ protected function defineEnvironment($app): void
'ping_interval' => 10,
'activity_timeout' => 30,
'max_message_size' => 1_000_000,
'max_connections' => 1,
]);

$app['config']->set('reverb.apps.apps.2', [
Expand Down
23 changes: 23 additions & 0 deletions tests/Unit/Protocols/Pusher/ServerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,29 @@
],
]);

it('it rejects a connection when the app is over the connection limit', function () {
$this->app['config']->set('reverb.apps.apps.0.max_connections', 1);
$this->server->open($connection = new FakeConnection);
$this->server->message(
$connection,
json_encode([
'event' => 'pusher:subscribe',
'data' => [
'channel' => 'my-channel',
],
])
);
$this->server->open($connectionTwo = new FakeConnection);

$connectionTwo->assertReceived([
'event' => 'pusher:error',
'data' => json_encode([
'code' => 4004,
'message' => 'Application is over connection quota',
]),
]);
});

it('sends an error if something fails for event type', function () {
$this->server->message(
$connection = new FakeConnection,
Expand Down