2018-11-21 11:13:40 +00:00
|
|
|
<?php
|
|
|
|
|
|
2018-11-27 20:41:12 +00:00
|
|
|
namespace BeyondCode\LaravelWebSockets\WebSockets;
|
2018-11-21 11:13:40 +00:00
|
|
|
|
2018-12-01 12:57:02 +00:00
|
|
|
use BeyondCode\LaravelWebSockets\Apps\App;
|
2018-12-04 21:22:33 +00:00
|
|
|
use BeyondCode\LaravelWebSockets\Dashboard\DashboardLogger;
|
2020-03-04 09:58:39 +00:00
|
|
|
use BeyondCode\LaravelWebSockets\Facades\StatisticsLogger;
|
2020-09-03 13:31:19 +00:00
|
|
|
use BeyondCode\LaravelWebSockets\PubSub\ReplicationInterface;
|
2020-03-04 09:58:39 +00:00
|
|
|
use BeyondCode\LaravelWebSockets\QueryParameters;
|
2018-11-27 15:35:28 +00:00
|
|
|
use BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager;
|
2020-03-04 09:58:39 +00:00
|
|
|
use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\ConnectionsOverCapacity;
|
2020-08-18 13:04:52 +00:00
|
|
|
use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\OriginNotAllowed;
|
2018-11-27 15:35:28 +00:00
|
|
|
use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\UnknownAppKey;
|
2018-12-04 21:22:33 +00:00
|
|
|
use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\WebSocketException;
|
|
|
|
|
use BeyondCode\LaravelWebSockets\WebSockets\Messages\PusherMessageFactory;
|
2020-03-04 09:58:39 +00:00
|
|
|
use Exception;
|
|
|
|
|
use Ratchet\ConnectionInterface;
|
|
|
|
|
use Ratchet\RFC6455\Messaging\MessageInterface;
|
|
|
|
|
use Ratchet\WebSocket\MessageComponentInterface;
|
2020-09-04 14:50:49 +00:00
|
|
|
use React\Promise\PromiseInterface;
|
2018-11-21 11:13:40 +00:00
|
|
|
|
2018-11-27 20:06:04 +00:00
|
|
|
class WebSocketHandler implements MessageComponentInterface
|
2018-11-21 11:13:40 +00:00
|
|
|
{
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* The channel manager.
|
|
|
|
|
*
|
|
|
|
|
* @var \BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager
|
|
|
|
|
*/
|
2018-11-21 11:13:40 +00:00
|
|
|
protected $channelManager;
|
|
|
|
|
|
2020-09-03 13:31:19 +00:00
|
|
|
/**
|
|
|
|
|
* The replicator client.
|
|
|
|
|
*
|
|
|
|
|
* @var ReplicationInterface
|
|
|
|
|
*/
|
|
|
|
|
protected $replicator;
|
|
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Initialize a new handler.
|
|
|
|
|
*
|
|
|
|
|
* @param \BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager $channelManager
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
2018-11-24 22:52:55 +00:00
|
|
|
public function __construct(ChannelManager $channelManager)
|
2018-11-21 11:13:40 +00:00
|
|
|
{
|
|
|
|
|
$this->channelManager = $channelManager;
|
2020-09-03 13:31:19 +00:00
|
|
|
$this->replicator = app(ReplicationInterface::class);
|
2018-11-21 11:13:40 +00:00
|
|
|
}
|
|
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Handle the socket opening.
|
|
|
|
|
*
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
2018-11-30 16:24:55 +00:00
|
|
|
public function onOpen(ConnectionInterface $connection)
|
2018-11-21 11:13:40 +00:00
|
|
|
{
|
2020-08-18 17:21:22 +00:00
|
|
|
$this->verifyAppKey($connection)
|
2020-08-18 13:04:52 +00:00
|
|
|
->verifyOrigin($connection)
|
2019-05-11 06:48:33 +00:00
|
|
|
->limitConcurrentConnections($connection)
|
2018-11-26 23:13:22 +00:00
|
|
|
->generateSocketId($connection)
|
|
|
|
|
->establishConnection($connection);
|
2018-11-21 11:13:40 +00:00
|
|
|
}
|
|
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Handle the incoming message.
|
|
|
|
|
*
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
|
|
|
|
* @param \Ratchet\RFC6455\Messaging\MessageInterface $message
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
2018-11-22 07:31:45 +00:00
|
|
|
public function onMessage(ConnectionInterface $connection, MessageInterface $message)
|
2018-11-21 11:13:40 +00:00
|
|
|
{
|
2018-12-01 14:10:01 +00:00
|
|
|
$message = PusherMessageFactory::createForMessage($message, $connection, $this->channelManager);
|
2018-11-21 11:13:40 +00:00
|
|
|
|
2018-11-21 22:47:46 +00:00
|
|
|
$message->respond();
|
2018-12-03 12:33:20 +00:00
|
|
|
|
2020-08-23 10:39:59 +00:00
|
|
|
StatisticsLogger::webSocketMessage($connection->app->id);
|
2018-11-21 11:13:40 +00:00
|
|
|
}
|
|
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Handle the websocket close.
|
|
|
|
|
*
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
2018-11-21 21:36:50 +00:00
|
|
|
public function onClose(ConnectionInterface $connection)
|
|
|
|
|
{
|
|
|
|
|
$this->channelManager->removeFromAllChannels($connection);
|
2018-12-03 10:10:02 +00:00
|
|
|
|
2020-08-17 18:06:51 +00:00
|
|
|
DashboardLogger::log($connection->app->id, DashboardLogger::TYPE_DISCONNECTED, [
|
|
|
|
|
'socketId' => $connection->socketId,
|
|
|
|
|
]);
|
2018-12-03 12:33:20 +00:00
|
|
|
|
2020-08-23 10:39:59 +00:00
|
|
|
StatisticsLogger::disconnection($connection->app->id);
|
2020-09-03 13:31:19 +00:00
|
|
|
|
|
|
|
|
$this->replicator->unsubscribeFromApp($connection->app->id);
|
2018-11-21 21:36:50 +00:00
|
|
|
}
|
2018-11-22 09:54:51 +00:00
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Handle the websocket errors.
|
|
|
|
|
*
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
|
|
|
|
* @param WebSocketException $exception
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
2018-11-26 23:13:22 +00:00
|
|
|
public function onError(ConnectionInterface $connection, Exception $exception)
|
2018-11-22 09:54:51 +00:00
|
|
|
{
|
2018-11-27 20:06:04 +00:00
|
|
|
if ($exception instanceof WebSocketException) {
|
2018-11-22 09:54:51 +00:00
|
|
|
$connection->send(json_encode(
|
|
|
|
|
$exception->getPayload()
|
|
|
|
|
));
|
|
|
|
|
}
|
2020-09-03 13:31:19 +00:00
|
|
|
|
|
|
|
|
$this->replicator->unsubscribeFromApp($connection->app->id);
|
2018-11-22 09:54:51 +00:00
|
|
|
}
|
2018-11-22 10:29:12 +00:00
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Verify the app key validity.
|
|
|
|
|
*
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
|
|
|
|
* @return $this
|
|
|
|
|
*/
|
2018-12-01 12:40:18 +00:00
|
|
|
protected function verifyAppKey(ConnectionInterface $connection)
|
2018-11-22 10:29:12 +00:00
|
|
|
{
|
2018-11-25 23:42:45 +00:00
|
|
|
$appKey = QueryParameters::create($connection->httpRequest)->get('appKey');
|
2018-11-22 10:30:57 +00:00
|
|
|
|
2018-12-04 21:22:33 +00:00
|
|
|
if (! $app = App::findByKey($appKey)) {
|
2018-11-26 23:13:22 +00:00
|
|
|
throw new UnknownAppKey($appKey);
|
2018-11-22 10:29:12 +00:00
|
|
|
}
|
2018-11-22 16:59:56 +00:00
|
|
|
|
2018-12-01 13:12:15 +00:00
|
|
|
$connection->app = $app;
|
2018-11-26 23:13:22 +00:00
|
|
|
|
|
|
|
|
return $this;
|
2018-11-22 10:29:12 +00:00
|
|
|
}
|
2018-11-22 10:30:57 +00:00
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Verify the origin.
|
|
|
|
|
*
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
|
|
|
|
* @return $this
|
|
|
|
|
*/
|
2020-08-18 13:04:52 +00:00
|
|
|
protected function verifyOrigin(ConnectionInterface $connection)
|
|
|
|
|
{
|
|
|
|
|
if (! $connection->app->allowedOrigins) {
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$header = (string) ($connection->httpRequest->getHeader('Origin')[0] ?? null);
|
|
|
|
|
|
|
|
|
|
$origin = parse_url($header, PHP_URL_HOST) ?: $header;
|
|
|
|
|
|
|
|
|
|
if (! $header || ! in_array($origin, $connection->app->allowedOrigins)) {
|
|
|
|
|
throw new OriginNotAllowed($connection->app->key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Limit the connections count by the app.
|
|
|
|
|
*
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
|
|
|
|
* @return $this
|
|
|
|
|
*/
|
2019-05-11 06:48:33 +00:00
|
|
|
protected function limitConcurrentConnections(ConnectionInterface $connection)
|
|
|
|
|
{
|
|
|
|
|
if (! is_null($capacity = $connection->app->capacity)) {
|
2020-09-04 08:34:33 +00:00
|
|
|
$connectionsCount = $this->channelManager->getGlobalConnectionsCount($connection->app->id);
|
2020-08-24 09:43:59 +00:00
|
|
|
|
2020-09-04 14:50:49 +00:00
|
|
|
if ($connectionsCount instanceof PromiseInterface) {
|
|
|
|
|
$connectionsCount->then(function ($connectionsCount) use ($capacity, $connection) {
|
2020-09-06 17:29:33 +00:00
|
|
|
$connectionsCount = $connectionsCount ?: 0;
|
|
|
|
|
|
2020-09-04 14:50:49 +00:00
|
|
|
$this->sendExceptionIfOverCapacity($connectionsCount, $capacity, $connection);
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
$this->throwExceptionIfOverCapacity($connectionsCount, $capacity);
|
2019-05-11 06:48:33 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Create a socket id.
|
|
|
|
|
*
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
|
|
|
|
* @return $this
|
|
|
|
|
*/
|
2018-12-01 12:40:18 +00:00
|
|
|
protected function generateSocketId(ConnectionInterface $connection)
|
|
|
|
|
{
|
2018-12-04 21:22:33 +00:00
|
|
|
$socketId = sprintf('%d.%d', random_int(1, 1000000000), random_int(1, 1000000000));
|
2018-12-01 12:40:18 +00:00
|
|
|
|
|
|
|
|
$connection->socketId = $socketId;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Establish connection with the client.
|
|
|
|
|
*
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
|
|
|
|
* @return $this
|
|
|
|
|
*/
|
2018-11-22 10:30:57 +00:00
|
|
|
protected function establishConnection(ConnectionInterface $connection)
|
|
|
|
|
{
|
|
|
|
|
$connection->send(json_encode([
|
|
|
|
|
'event' => 'pusher:connection_established',
|
|
|
|
|
'data' => json_encode([
|
|
|
|
|
'socket_id' => $connection->socketId,
|
2018-11-28 16:09:50 +00:00
|
|
|
'activity_timeout' => 30,
|
2018-12-04 21:22:33 +00:00
|
|
|
]),
|
2018-11-22 10:30:57 +00:00
|
|
|
]));
|
2018-11-26 21:18:00 +00:00
|
|
|
|
2020-08-17 18:06:51 +00:00
|
|
|
/** @var \GuzzleHttp\Psr7\Request $request */
|
|
|
|
|
$request = $connection->httpRequest;
|
2018-11-22 10:30:57 +00:00
|
|
|
|
2020-08-17 18:06:51 +00:00
|
|
|
DashboardLogger::log($connection->app->id, DashboardLogger::TYPE_CONNECTED, [
|
|
|
|
|
'origin' => "{$request->getUri()->getScheme()}://{$request->getUri()->getHost()}",
|
|
|
|
|
'socketId' => $connection->socketId,
|
|
|
|
|
]);
|
2018-12-03 12:27:45 +00:00
|
|
|
|
2020-08-23 10:39:59 +00:00
|
|
|
StatisticsLogger::connection($connection->app->id);
|
2020-08-17 18:20:50 +00:00
|
|
|
|
2020-09-03 13:31:19 +00:00
|
|
|
$this->replicator->subscribeToApp($connection->app->id);
|
|
|
|
|
|
2018-11-26 23:13:22 +00:00
|
|
|
return $this;
|
2018-11-22 10:30:57 +00:00
|
|
|
}
|
2020-09-04 14:50:49 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Throw a ConnectionsOverCapacity exception.
|
|
|
|
|
*
|
|
|
|
|
* @param int $connectionsCount
|
|
|
|
|
* @param int $capacity
|
|
|
|
|
* @return void
|
|
|
|
|
* @throws ConnectionsOverCapacity
|
|
|
|
|
*/
|
|
|
|
|
protected function throwExceptionIfOverCapacity(int $connectionsCount, int $capacity)
|
|
|
|
|
{
|
|
|
|
|
if ($connectionsCount >= $capacity) {
|
|
|
|
|
throw new ConnectionsOverCapacity;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Send the ConnectionsOverCapacity exception through
|
|
|
|
|
* the connection and close the channel.
|
|
|
|
|
*
|
|
|
|
|
* @param int $connectionsCount
|
|
|
|
|
* @param int $capacity
|
|
|
|
|
* @param ConnectionInterface $connection
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
protected function sendExceptionIfOverCapacity(int $connectionsCount, int $capacity, ConnectionInterface $connection)
|
|
|
|
|
{
|
|
|
|
|
if ($connectionsCount >= $capacity) {
|
|
|
|
|
$payload = json_encode((new ConnectionsOverCapacity)->getPayload());
|
|
|
|
|
|
|
|
|
|
tap($connection)->send($payload)->close();
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-12-04 21:22:33 +00:00
|
|
|
}
|