From 8758b04099b55d571de0fdb91f6146429ed3065c Mon Sep 17 00:00:00 2001 From: Alex Renoki Date: Sat, 26 Sep 2020 20:57:19 +0300 Subject: [PATCH 1/7] Dispatching events --- docs/advanced-usage/dispatched-events.md | 82 +++++++++++++++++++ .../non-blocking-queue-driver.md | 30 +++++++ docs/horizontal-scaling/redis.md | 26 ------ src/Channels/Channel.php | 14 ++++ src/Channels/PresenceChannel.php | 16 ++++ src/Events/ConnectionClosed.php | 38 +++++++++ src/Events/ConnectionPonged.php | 38 +++++++++ src/Events/NewConnection.php | 38 +++++++++ src/Events/SubscribedToChannel.php | 56 +++++++++++++ src/Events/UnsubscribedFromChannel.php | 56 +++++++++++++ src/Events/WebSocketMessageReceived.php | 56 +++++++++++++ .../Messages/PusherChannelProtocolMessage.php | 3 + src/Server/WebSocketHandler.php | 13 +++ 13 files changed, 440 insertions(+), 26 deletions(-) create mode 100644 docs/advanced-usage/dispatched-events.md create mode 100644 docs/advanced-usage/non-blocking-queue-driver.md create mode 100644 src/Events/ConnectionClosed.php create mode 100644 src/Events/ConnectionPonged.php create mode 100644 src/Events/NewConnection.php create mode 100644 src/Events/SubscribedToChannel.php create mode 100644 src/Events/UnsubscribedFromChannel.php create mode 100644 src/Events/WebSocketMessageReceived.php diff --git a/docs/advanced-usage/dispatched-events.md b/docs/advanced-usage/dispatched-events.md new file mode 100644 index 0000000000..be5e095b24 --- /dev/null +++ b/docs/advanced-usage/dispatched-events.md @@ -0,0 +1,82 @@ +--- +title: Dispatched Events +order: 5 +--- + +# Dispatched Events + +Laravel WebSockets takes advantage of Laravel's Event dispatching observer, in a way that you can handle in-server events outside of it. + +For example, you can listen for events like when a new connection establishes or when an user joins a presence channel. + +## Events + +Below you will find a list of dispatched events: + +- `BeyondCode\LaravelWebSockets\Events\NewConnection` - when a connection successfully establishes on the server +- `BeyondCode\LaravelWebSockets\Events\ConnectionClosed` - when a connection leaves the server +- `BeyondCode\LaravelWebSockets\Events\SubscribedToChannel` - when a connection subscribes to a specific channel +- `BeyondCode\LaravelWebSockets\Events\UnsubscribedFromChannel` - when a connection unsubscribes from a specific channel +- `BeyondCode\LaravelWebSockets\Events\WebSocketMessageReceived` - when the server receives a message +- `BeyondCode\LaravelWebSockets\EventsConnectionPonged` - when a connection pings to the server that it is still alive + +## Queued Listeners + +Because the default Redis connection (either PhpRedis or Predis) is a blocking I/O method and can cause problems with the server speed and availability, you might want to check the [Non-Blocking Queue Driver](non-blocking-queue-driver.md) documentation that helps you create the Async Redis queue driver that is going to fix the Blocking I/O issue. + +If set up, you can use the `async-redis` queue driver in your listeners: + +```php + [ + App\Listeners\HandleNewConnections::class, + ], +]; +``` diff --git a/docs/advanced-usage/non-blocking-queue-driver.md b/docs/advanced-usage/non-blocking-queue-driver.md new file mode 100644 index 0000000000..98ed10d1a8 --- /dev/null +++ b/docs/advanced-usage/non-blocking-queue-driver.md @@ -0,0 +1,30 @@ +--- +title: Non-Blocking Queue Driver +order: 4 +--- + +# Non-Blocking Queue Driver + +In Laravel, he default Redis connection also interacts with the queues. Since you might want to dispatch jobs on Redis from the server, you can encounter an anti-pattern of using a blocking I/O connection (like PhpRedis or PRedis) within the WebSockets server. + +To solve this issue, you can configure the built-in queue driver that uses the Async Redis connection when it's possible, like within the WebSockets server. It's highly recommended to switch your queue to it if you are going to use the queues within the server controllers, for example. + +Add the `async-redis` queue driver to your list of connections. The configuration parameters are compatible with the default `redis` driver: + +```php +'connections' => [ + 'async-redis' => [ + 'driver' => 'async-redis', + 'connection' => env('WEBSOCKETS_REDIS_REPLICATION_CONNECTION', 'default'), + 'queue' => env('REDIS_QUEUE', 'default'), + 'retry_after' => 90, + 'block_for' => null, + ], +] +``` + +Also, make sure that the default queue driver is set to `async-redis`: + +``` +QUEUE_CONNECTION=async-redis +``` diff --git a/docs/horizontal-scaling/redis.md b/docs/horizontal-scaling/redis.md index 86759db300..4f6383583b 100644 --- a/docs/horizontal-scaling/redis.md +++ b/docs/horizontal-scaling/redis.md @@ -40,29 +40,3 @@ You can set the connection name to the Redis database under `redis`: ``` The connections can be found in your `config/database.php` file, under the `redis` key. - -## Async Redis Queue - -The default Redis connection also interacts with the queues. Since you might want to dispatch jobs on Redis from the server, you can encounter an anti-pattern of using a blocking I/O connection (like PhpRedis or PRedis) within the WebSockets server. - -To solve this issue, you can configure the built-in queue driver that uses the Async Redis connection when it's possible, like within the WebSockets server. It's highly recommended to switch your queue to it if you are going to use the queues within the server controllers, for example. - -Add the `async-redis` queue driver to your list of connections. The configuration parameters are compatible with the default `redis` driver: - -```php -'connections' => [ - 'async-redis' => [ - 'driver' => 'async-redis', - 'connection' => env('WEBSOCKETS_REDIS_REPLICATION_CONNECTION', 'default'), - 'queue' => env('REDIS_QUEUE', 'default'), - 'retry_after' => 90, - 'block_for' => null, - ], -] -``` - -Also, make sure that the default queue driver is set to `async-redis`: - -``` -QUEUE_CONNECTION=async-redis -``` diff --git a/src/Channels/Channel.php b/src/Channels/Channel.php index e64a4d1ac2..fd857e233f 100644 --- a/src/Channels/Channel.php +++ b/src/Channels/Channel.php @@ -4,6 +4,8 @@ use BeyondCode\LaravelWebSockets\Contracts\ChannelManager; use BeyondCode\LaravelWebSockets\DashboardLogger; +use BeyondCode\LaravelWebSockets\Events\SubscribedToChannel; +use BeyondCode\LaravelWebSockets\Events\UnsubscribedFromChannel; use BeyondCode\LaravelWebSockets\Server\Exceptions\InvalidSignature; use Illuminate\Support\Str; use Ratchet\ConnectionInterface; @@ -89,6 +91,12 @@ public function subscribe(ConnectionInterface $connection, stdClass $payload): b 'channel' => $this->getName(), ]); + SubscribedToChannel::dispatch( + $connection->app->id, + $connection->socketId, + $this->getName(), + ); + return true; } @@ -106,6 +114,12 @@ public function unsubscribe(ConnectionInterface $connection): bool unset($this->connections[$connection->socketId]); + UnsubscribedFromChannel::dispatch( + $connection->app->id, + $connection->socketId, + $this->getName() + ); + return true; } diff --git a/src/Channels/PresenceChannel.php b/src/Channels/PresenceChannel.php index 3191be4c9d..0c52c4d43e 100644 --- a/src/Channels/PresenceChannel.php +++ b/src/Channels/PresenceChannel.php @@ -3,6 +3,8 @@ namespace BeyondCode\LaravelWebSockets\Channels; use BeyondCode\LaravelWebSockets\DashboardLogger; +use BeyondCode\LaravelWebSockets\Events\SubscribedToChannel; +use BeyondCode\LaravelWebSockets\Events\UnsubscribedFromChannel; use BeyondCode\LaravelWebSockets\Server\Exceptions\InvalidSignature; use Ratchet\ConnectionInterface; use stdClass; @@ -72,6 +74,13 @@ public function subscribe(ConnectionInterface $connection, stdClass $payload): b (object) $memberAddedPayload, $connection->socketId, $connection->app->id ); + + SubscribedToPresenceChannel::dispatch( + $connection->app->id, + $connection->socketId, + $this->getName(), + $user + ); } DashboardLogger::log($connection->app->id, DashboardLogger::TYPE_SUBSCRIBED, [ @@ -128,6 +137,13 @@ public function unsubscribe(ConnectionInterface $connection): bool (object) $memberRemovedPayload, $connection->socketId, $connection->app->id ); + + UnsubscribedFromChannel::dispatch( + $connection->app->id, + $connection->socketId, + $this->getName(), + $user + ); } }); }); diff --git a/src/Events/ConnectionClosed.php b/src/Events/ConnectionClosed.php new file mode 100644 index 0000000000..60b810be4c --- /dev/null +++ b/src/Events/ConnectionClosed.php @@ -0,0 +1,38 @@ +appId = $appId; + $this->socketId = $socketId; + } +} diff --git a/src/Events/ConnectionPonged.php b/src/Events/ConnectionPonged.php new file mode 100644 index 0000000000..43440ebf64 --- /dev/null +++ b/src/Events/ConnectionPonged.php @@ -0,0 +1,38 @@ +appId = $appId; + $this->socketId = $socketId; + } +} diff --git a/src/Events/NewConnection.php b/src/Events/NewConnection.php new file mode 100644 index 0000000000..5c8a30fe46 --- /dev/null +++ b/src/Events/NewConnection.php @@ -0,0 +1,38 @@ +appId = $appId; + $this->socketId = $socketId; + } +} diff --git a/src/Events/SubscribedToChannel.php b/src/Events/SubscribedToChannel.php new file mode 100644 index 0000000000..5832b2a24d --- /dev/null +++ b/src/Events/SubscribedToChannel.php @@ -0,0 +1,56 @@ +appId = $appId; + $this->socketId = $socketId; + $this->channelName = $channelName; + $this->user = $user; + } +} diff --git a/src/Events/UnsubscribedFromChannel.php b/src/Events/UnsubscribedFromChannel.php new file mode 100644 index 0000000000..6e16c130c6 --- /dev/null +++ b/src/Events/UnsubscribedFromChannel.php @@ -0,0 +1,56 @@ +appId = $appId; + $this->socketId = $socketId; + $this->channelName = $channelName; + $this->user = $user; + } +} diff --git a/src/Events/WebSocketMessageReceived.php b/src/Events/WebSocketMessageReceived.php new file mode 100644 index 0000000000..442ecb7bba --- /dev/null +++ b/src/Events/WebSocketMessageReceived.php @@ -0,0 +1,56 @@ +appId = $appId; + $this->socketId = $socketId; + $this->message = $message; + $this->decodedMessage = json_decode($message->getPayload(), true); + } +} diff --git a/src/Server/Messages/PusherChannelProtocolMessage.php b/src/Server/Messages/PusherChannelProtocolMessage.php index 6385d90834..446806e133 100644 --- a/src/Server/Messages/PusherChannelProtocolMessage.php +++ b/src/Server/Messages/PusherChannelProtocolMessage.php @@ -2,6 +2,7 @@ namespace BeyondCode\LaravelWebSockets\Server\Messages; +use BeyondCode\LaravelWebSockets\Events\ConnectionPonged; use Illuminate\Support\Str; use Ratchet\ConnectionInterface; use stdClass; @@ -35,6 +36,8 @@ protected function ping(ConnectionInterface $connection) ->connectionPonged($connection) ->then(function () use ($connection) { $connection->send(json_encode(['event' => 'pusher:pong'])); + + ConnectionPonged::disptach($connection->app->id, $connection->socketId); }); } diff --git a/src/Server/WebSocketHandler.php b/src/Server/WebSocketHandler.php index 9fd3fe2d4d..8bec3895a8 100644 --- a/src/Server/WebSocketHandler.php +++ b/src/Server/WebSocketHandler.php @@ -5,6 +5,9 @@ use BeyondCode\LaravelWebSockets\Apps\App; use BeyondCode\LaravelWebSockets\Contracts\ChannelManager; use BeyondCode\LaravelWebSockets\DashboardLogger; +use BeyondCode\LaravelWebSockets\Events\ConnectionClosed; +use BeyondCode\LaravelWebSockets\Events\NewConnection; +use BeyondCode\LaravelWebSockets\Events\WebSocketMessageReceived; use BeyondCode\LaravelWebSockets\Facades\StatisticsCollector; use Exception; use Ratchet\ConnectionInterface; @@ -63,6 +66,8 @@ public function onOpen(ConnectionInterface $connection) 'origin' => "{$request->getUri()->getScheme()}://{$request->getUri()->getHost()}", 'socketId' => $connection->socketId, ]); + + NewConnection::dispatch($connection->app->id, $connection->socketId); } } @@ -84,6 +89,12 @@ public function onMessage(ConnectionInterface $connection, MessageInterface $mes )->respond(); StatisticsCollector::webSocketMessage($connection->app->id); + + WebSocketMessageReceived::dispatch( + $connection->app->id, + $connection->socketId, + $message + ); } /** @@ -105,6 +116,8 @@ public function onClose(ConnectionInterface $connection) DashboardLogger::log($connection->app->id, DashboardLogger::TYPE_DISCONNECTED, [ 'socketId' => $connection->socketId, ]); + + ConnectionClosed::dispatch($connection->app->id, $connection->socketId); } }); } From f281624393af21aa3aee1f7662e24a8eedb458b9 Mon Sep 17 00:00:00 2001 From: rennokki Date: Sat, 26 Sep 2020 17:57:39 +0000 Subject: [PATCH 2/7] Apply fixes from StyleCI (#555) --- src/Channels/PresenceChannel.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Channels/PresenceChannel.php b/src/Channels/PresenceChannel.php index 0c52c4d43e..cc0f9807de 100644 --- a/src/Channels/PresenceChannel.php +++ b/src/Channels/PresenceChannel.php @@ -3,7 +3,6 @@ namespace BeyondCode\LaravelWebSockets\Channels; use BeyondCode\LaravelWebSockets\DashboardLogger; -use BeyondCode\LaravelWebSockets\Events\SubscribedToChannel; use BeyondCode\LaravelWebSockets\Events\UnsubscribedFromChannel; use BeyondCode\LaravelWebSockets\Server\Exceptions\InvalidSignature; use Ratchet\ConnectionInterface; From 4e07830d0cfd68de9ddea052747d76e7d3f98587 Mon Sep 17 00:00:00 2001 From: Alex Renoki Date: Sat, 26 Sep 2020 21:26:04 +0300 Subject: [PATCH 3/7] fixed event name --- src/Channels/PresenceChannel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Channels/PresenceChannel.php b/src/Channels/PresenceChannel.php index 0c52c4d43e..691b082832 100644 --- a/src/Channels/PresenceChannel.php +++ b/src/Channels/PresenceChannel.php @@ -75,7 +75,7 @@ public function subscribe(ConnectionInterface $connection, stdClass $payload): b $connection->app->id ); - SubscribedToPresenceChannel::dispatch( + SubscribedToChannel::dispatch( $connection->app->id, $connection->socketId, $this->getName(), From c027c475e4f384e71f92e9d29959a3785fffe148 Mon Sep 17 00:00:00 2001 From: Alex Renoki Date: Sat, 26 Sep 2020 21:29:14 +0300 Subject: [PATCH 4/7] fixed import --- src/Channels/PresenceChannel.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Channels/PresenceChannel.php b/src/Channels/PresenceChannel.php index 666edf9ec6..691b082832 100644 --- a/src/Channels/PresenceChannel.php +++ b/src/Channels/PresenceChannel.php @@ -3,6 +3,7 @@ namespace BeyondCode\LaravelWebSockets\Channels; use BeyondCode\LaravelWebSockets\DashboardLogger; +use BeyondCode\LaravelWebSockets\Events\SubscribedToChannel; use BeyondCode\LaravelWebSockets\Events\UnsubscribedFromChannel; use BeyondCode\LaravelWebSockets\Server\Exceptions\InvalidSignature; use Ratchet\ConnectionInterface; From f28a3231c6f93060e96fd32759048ba28371e755 Mon Sep 17 00:00:00 2001 From: Alex Renoki Date: Sat, 26 Sep 2020 21:32:04 +0300 Subject: [PATCH 5/7] Fixing use --- src/Channels/PresenceChannel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Channels/PresenceChannel.php b/src/Channels/PresenceChannel.php index 691b082832..614fe8da50 100644 --- a/src/Channels/PresenceChannel.php +++ b/src/Channels/PresenceChannel.php @@ -62,7 +62,7 @@ public function subscribe(ConnectionInterface $connection, stdClass $payload): b // and in this case the events will only be triggered when the first tab is opened. $this->channelManager ->getMemberSockets($user->user_id, $connection->app->id, $this->getName()) - ->then(function ($sockets) use ($payload, $connection) { + ->then(function ($sockets) use ($payload, $connection, $user) { if (count($sockets) === 1) { $memberAddedPayload = [ 'event' => 'pusher_internal:member_added', From 52223ed27a407c5e4cba9cfd4e3f04594c76da9a Mon Sep 17 00:00:00 2001 From: Alex Renoki Date: Sat, 26 Sep 2020 21:34:44 +0300 Subject: [PATCH 6/7] Added missing stdClass --- src/Events/SubscribedToChannel.php | 1 + src/Events/UnsubscribedFromChannel.php | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Events/SubscribedToChannel.php b/src/Events/SubscribedToChannel.php index 5832b2a24d..b3109f7f89 100644 --- a/src/Events/SubscribedToChannel.php +++ b/src/Events/SubscribedToChannel.php @@ -4,6 +4,7 @@ use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; +use stdClass; class SubscribedToChannel { diff --git a/src/Events/UnsubscribedFromChannel.php b/src/Events/UnsubscribedFromChannel.php index 6e16c130c6..6e132e74b1 100644 --- a/src/Events/UnsubscribedFromChannel.php +++ b/src/Events/UnsubscribedFromChannel.php @@ -4,6 +4,7 @@ use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; +use stdClass; class UnsubscribedFromChannel { From 2dfbe9d203d0dca066eea2df20e1f9119a6a8bb7 Mon Sep 17 00:00:00 2001 From: Alex Renoki Date: Sat, 26 Sep 2020 21:36:59 +0300 Subject: [PATCH 7/7] typo --- src/Server/Messages/PusherChannelProtocolMessage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Server/Messages/PusherChannelProtocolMessage.php b/src/Server/Messages/PusherChannelProtocolMessage.php index 446806e133..c6f4f13472 100644 --- a/src/Server/Messages/PusherChannelProtocolMessage.php +++ b/src/Server/Messages/PusherChannelProtocolMessage.php @@ -37,7 +37,7 @@ protected function ping(ConnectionInterface $connection) ->then(function () use ($connection) { $connection->send(json_encode(['event' => 'pusher:pong'])); - ConnectionPonged::disptach($connection->app->id, $connection->socketId); + ConnectionPonged::dispatch($connection->app->id, $connection->socketId); }); }