Skip to content
This repository was archived by the owner on Feb 7, 2024. It is now read-only.

[refactor/tests] Added tests for Redis PubSub replication #453

Merged
merged 30 commits into from
Aug 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
64b0fa8
Removed the setupReplication trait
rennokki Aug 13, 2020
51f84e3
set up tests
rennokki Aug 13, 2020
d7038ed
Added Redis setup at run
rennokki Aug 13, 2020
64d11c4
Added redis as service
rennokki Aug 13, 2020
1446cf8
Running redis driver tests only on linux
rennokki Aug 13, 2020
4389fd1
Added soft default to replication driver check
rennokki Aug 13, 2020
0ebf223
Renamed the prop to replicator
rennokki Aug 13, 2020
7053600
Merge branch 'refactor/tests' of github.com:beyondcode/laravel-websoc…
rennokki Aug 13, 2020
b1d29d0
swap to xdebug
rennokki Aug 13, 2020
00b3edf
Added Illuminate\Broadcasting\BroadcastServiceProvider
rennokki Aug 13, 2020
ca9e90d
Setup redis only on ubuntu-latest
rennokki Aug 13, 2020
8f52393
Using only Redis 6.x
rennokki Aug 13, 2020
14f54da
$this->app->make
rennokki Aug 13, 2020
5838aca
Set up config for broadcasting
rennokki Aug 13, 2020
5997dd4
wip docblocks
rennokki Aug 14, 2020
4c23363
wip dashboard logger
rennokki Aug 14, 2020
7458c3e
Emptied tests for replication
rennokki Aug 14, 2020
22fcddb
docblocks
rennokki Aug 14, 2020
4c64493
Improved loggin display
rennokki Aug 14, 2020
25694c7
wip
rennokki Aug 14, 2020
5c3e87e
Apply fixes from StyleCI (#461)
rennokki Aug 14, 2020
50f0b70
wip
rennokki Aug 14, 2020
0fdc0bc
Merge branch 'refactor/pubsub-replication-tests' of github.com:beyond…
rennokki Aug 14, 2020
939ae17
Apply fixes from StyleCI (#462)
rennokki Aug 14, 2020
04cc2e7
missing class
rennokki Aug 14, 2020
4ae3d81
assert publish
rennokki Aug 14, 2020
92dd8f4
skipped some tests until further addo
rennokki Aug 14, 2020
b140d1f
Updated command run for windows envs
rennokki Aug 14, 2020
c543bbc
Updated command
rennokki Aug 14, 2020
b7a00ba
wip incomplete tests
rennokki Aug 14, 2020
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
21 changes: 18 additions & 3 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ jobs:
- name: Checkout code
uses: actions/checkout@v1

- name: Setup Redis
uses: supercharge/[email protected]
with:
redis-version: 6
if: ${{ matrix.os == 'ubuntu-latest' }}

- name: Cache dependencies
uses: actions/cache@v1
with:
Expand All @@ -35,16 +41,25 @@ jobs:
with:
php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick
coverage: pcov
coverage: xdebug

- name: Install dependencies
run: |
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest

- name: Execute tests
run: vendor/bin/phpunit --coverage-text --coverage-clover=coverage.xml
- name: Execute tests with Local driver
run: vendor/bin/phpunit --coverage-text --coverage-clover=coverage_local.xml
env:
REPLICATION_DRIVER: local

- name: Execute tests with Redis driver
run: vendor/bin/phpunit --coverage-text --coverage-clover=coverage_redis.xml
if: ${{ matrix.os == 'ubuntu-latest' }}
env:
REPLICATION_DRIVER: redis

- uses: codecov/codecov-action@v1
with:
fail_ci_if_error: false
file: '*.xml'
12 changes: 5 additions & 7 deletions config/websockets.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,23 +143,21 @@

/*
|--------------------------------------------------------------------------
| Broadcasting Replication
| Broadcasting Replication PubSub
|--------------------------------------------------------------------------
|
| You can enable replication to publish and subscribe to
| messages across the driver.
|
| By default, it is disabled, but you can configure it to use drivers

| By default, it is set to 'local', but you can configure it to use drivers
| like Redis to ensure connection between multiple instances of
| WebSocket servers.
| WebSocket servers. Just set the driver to 'redis' to enable the PubSub using Redis.
|
*/

'replication' => [

'enabled' => false,

'driver' => 'redis',
'driver' => 'local',

'redis' => [

Expand Down
6 changes: 3 additions & 3 deletions resources/views/dashboard.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,14 @@
<thead>
<tr>
<th>Type</th>
<th>Socket</th>
<th>Details</th>
<th>Time</th>
</tr>
</thead>
<tbody>
<tr v-for="log in logs.slice().reverse()">
<td><span class="badge" :class="getBadgeClass(log)">@{{ log.type }}</span></td>
<td>@{{ log.socketId }}</td>
<td>@{{ log.details }}</td>
<td><pre>@{{ log.details }}</pre></td>
<td>@{{ log.time }}</td>
</tr>
</tbody>
Expand Down Expand Up @@ -207,6 +205,8 @@
'subscribed',
'client-message',
'api-message',
'replicator-subscribed',
'replicator-unsubscribed',
].forEach(channelName => this.subscribeToChannel(channelName))
},

Expand Down
29 changes: 28 additions & 1 deletion src/Console/StartWebSocketServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use BeyondCode\LaravelWebSockets\Facades\StatisticsLogger;
use BeyondCode\LaravelWebSockets\Facades\WebSocketsRouter;
use BeyondCode\LaravelWebSockets\PubSub\Drivers\LocalClient;
use BeyondCode\LaravelWebSockets\PubSub\Drivers\RedisClient;
use BeyondCode\LaravelWebSockets\PubSub\ReplicationInterface;
use BeyondCode\LaravelWebSockets\Server\Logger\ConnectionLogger;
use BeyondCode\LaravelWebSockets\Server\Logger\HttpLogger;
Expand Down Expand Up @@ -53,6 +55,7 @@ public function handle()
->configureMessageLogger()
->configureConnectionLogger()
->configureRestartTimer()
->configurePubSub()
->registerEchoRoutes()
->registerCustomRoutes()
->configurePubSubReplication()
Expand Down Expand Up @@ -130,6 +133,28 @@ public function configureRestartTimer()
return $this;
}

/**
* Configure the replicators.
*
* @return void
*/
public function configurePubSub()
{
if (config('websockets.replication.driver', 'local') === 'local') {
$this->laravel->singleton(ReplicationInterface::class, function () {
return new LocalClient;
});
}

if (config('websockets.replication.driver', 'local') === 'redis') {
$this->laravel->singleton(ReplicationInterface::class, function () {
return (new RedisClient)->boot($this->loop);
});
}

return $this;
}

protected function registerEchoRoutes()
{
WebSocketsRouter::echo();
Expand Down Expand Up @@ -166,7 +191,9 @@ protected function startWebSocketServer()

protected function configurePubSubReplication()
{
$this->laravel->get(ReplicationInterface::class)->boot($this->loop);
$this->laravel
->get(ReplicationInterface::class)
->boot($this->loop);

return $this;
}
Expand Down
72 changes: 60 additions & 12 deletions src/Dashboard/DashboardLogger.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,68 +9,116 @@
class DashboardLogger
{
const LOG_CHANNEL_PREFIX = 'private-websockets-dashboard-';

const TYPE_DISCONNECTION = 'disconnection';

const TYPE_CONNECTION = 'connection';

const TYPE_VACATED = 'vacated';

const TYPE_OCCUPIED = 'occupied';

const TYPE_SUBSCRIBED = 'subscribed';

const TYPE_CLIENT_MESSAGE = 'client-message';

const TYPE_API_MESSAGE = 'api-message';

const TYPE_REPLICATOR_SUBSCRIBED = 'replicator-subscribed';

const TYPE_REPLICATOR_UNSUBSCRIBED = 'replicator-unsubscribed';

public static function connection(ConnectionInterface $connection)
{
/** @var \GuzzleHttp\Psr7\Request $request */
$request = $connection->httpRequest;

static::log($connection->app->id, static::TYPE_CONNECTION, [
'details' => "Origin: {$request->getUri()->getScheme()}://{$request->getUri()->getHost()}",
'socketId' => $connection->socketId,
'details' => [
'origin' => "{$request->getUri()->getScheme()}://{$request->getUri()->getHost()}",
'socketId' => $connection->socketId,
],
]);
}

public static function occupied(ConnectionInterface $connection, string $channelName)
{
static::log($connection->app->id, static::TYPE_OCCUPIED, [
'details' => "Channel: {$channelName}",
'details' => [
'channel' => $channelName,
],
]);
}

public static function subscribed(ConnectionInterface $connection, string $channelName)
{
static::log($connection->app->id, static::TYPE_SUBSCRIBED, [
'socketId' => $connection->socketId,
'details' => "Channel: {$channelName}",
'details' => [
'socketId' => $connection->socketId,
'channel' => $channelName,
],
]);
}

public static function clientMessage(ConnectionInterface $connection, stdClass $payload)
{
static::log($connection->app->id, static::TYPE_CLIENT_MESSAGE, [
'details' => "Channel: {$payload->channel}, Event: {$payload->event}",
'socketId' => $connection->socketId,
'data' => json_encode($payload),
'details' => [
'socketId' => $connection->socketId,
'channel' => $payload->channel,
'event' => $payload->event,
'data' => $payload,
],
]);
}

public static function disconnection(ConnectionInterface $connection)
{
static::log($connection->app->id, static::TYPE_DISCONNECTION, [
'socketId' => $connection->socketId,
'details' => [
'socketId' => $connection->socketId,
],
]);
}

public static function vacated(ConnectionInterface $connection, string $channelName)
{
static::log($connection->app->id, static::TYPE_VACATED, [
'details' => "Channel: {$channelName}",
'details' => [
'socketId' => $connection->socketId,
'channel' => $channelName,
],
]);
}

public static function apiMessage($appId, string $channel, string $event, string $payload)
{
static::log($appId, static::TYPE_API_MESSAGE, [
'details' => "Channel: {$channel}, Event: {$event}",
'data' => $payload,
'details' => [
'channel' => $connection,
'event' => $event,
'payload' => $payload,
],
]);
}

public static function replicatorSubscribed(string $appId, string $channel, string $serverId)
{
static::log($appId, static::TYPE_REPLICATOR_SUBSCRIBED, [
'details' => [
'serverId' => $serverId,
'channel' => $channel,
],
]);
}

public static function replicatorUnsubscribed(string $appId, string $channel, string $serverId)
{
static::log($appId, static::TYPE_REPLICATOR_UNSUBSCRIBED, [
'details' => [
'serverId' => $serverId,
'channel' => $channel,
],
]);
}

Expand Down
28 changes: 16 additions & 12 deletions src/HttpApi/Controllers/FetchChannelsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,19 @@
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use stdClass;
use Symfony\Component\HttpKernel\Exception\HttpException;

class FetchChannelsController extends Controller
{
/** @var ReplicationInterface */
protected $replication;
protected $replicator;

public function __construct(ChannelManager $channelManager, ReplicationInterface $replication)
public function __construct(ChannelManager $channelManager, ReplicationInterface $replicator)
{
parent::__construct($channelManager);

$this->replication = $replication;
$this->replicator = $replicator;
}

public function __invoke(Request $request)
Expand Down Expand Up @@ -51,18 +52,21 @@ public function __invoke(Request $request)

// We ask the replication backend to get us the member count per channel.
// We get $counts back as a key-value array of channel names and their member count.
return $this->replication
return $this->replicator
->channelMemberCounts($request->appId, $channelNames)
->then(function (array $counts) use ($channels, $attributes) {
return [
'channels' => $channels->map(function (PresenceChannel $channel) use ($counts, $attributes) {
$info = new \stdClass;
if (in_array('user_count', $attributes)) {
$info->user_count = $counts[$channel->getChannelName()];
}
$channels = $channels->map(function (PresenceChannel $channel) use ($counts, $attributes) {
$info = new stdClass;

if (in_array('user_count', $attributes)) {
$info->user_count = $counts[$channel->getChannelName()];
}

return $info;
})->toArray() ?: new \stdClass,
return $info;
})->toArray();

return [
'channels' => $channels ?: new stdClass,
];
});
}
Expand Down
Loading