public async Task HandleAsync(IConnection connection, CancellationToken cancellationToken)
        {
            ConnectedClient connectedClient;

            try
            {
                connectedClient = await _connectionInitializer.ConnectAsync(connection, cancellationToken).ConfigureAwait(false);
            }
            catch
            {
                try
                {
                    await connection.SendAsync(new Disconnected($"Error during connection initialization."), cancellationToken)
                    .ConfigureAwait(false);
                } catch { }

                throw;
            }

            _connectedClients.Add(connectedClient);
            if (!_connectedClients.IsClientConnected(connectedClient.ClientId))
            {
                throw new InvalidOperationException("Client was not added correctly.");
            }

            await TrySendPendingUpdates(connectedClient.Group, cancellationToken).ConfigureAwait(false);

            while (_connectedClients.IsClientConnected(connectedClient.ClientId))
            {
                try
                {
                    var message = await connection.ReceiveAsync(cancellationToken).ConfigureAwait(false);

                    await DispatchMessageAsync(connectedClient, message, cancellationToken).ConfigureAwait(false);
                }
                catch
                {
                    _connectedClients.Remove(connectedClient.ClientId);
                    throw; // If you delete this line and have ncrunch, your PC will die.
                }
                finally
                {
                    await TrySendPendingUpdates(connectedClient.Group, cancellationToken).ConfigureAwait(false);
                }
            }
        }
Example #2
0
    public ValueTask HandleAsync(ConnectedClient sender, Disconnect message, CancellationToken cancellationToken)
    {
        _store.Remove(sender.ClientId);

        return(sender.Connection.SendAsync(new Disconnected("Requested."), cancellationToken));
    }
    public async Task HandleAsync(IConnection connection, CancellationToken cancellationToken)
    {
        ConnectedClient connectedClient;

        connection = connection.WithReceiveAcknowledgement();
        var unwrapperConnection = new ServerMessageUnwrapperConnection(connection);

        try
        {
            connectedClient = await _connectionInitializer.ConnectAsync(unwrapperConnection, cancellationToken).ConfigureAwait(false);
        }
        catch
        {
            try
            {
                await connection.SendAsync(new Disconnected($"Error during connection initialization."), cancellationToken)
                .ConfigureAwait(false);
            }
            catch { }

            throw;
        }

        _connectedClients.Add(connectedClient);
        if (!_connectedClients.IsClientConnected(connectedClient.ClientId))
        {
            throw new InvalidOperationException("Client was not added correctly.");
        }

        foreach (var initializer in _connectedClientInitializers)
        {
            await initializer.InitializeAsync(connectedClient)
            .ConfigureAwait(false);
        }

        // TODO: Unit test all the logic about idempotency.
        var idempotencyKeys = new Dictionary <string, DateTime>();

        // TODO: Send only to groups that were specified in Metadata from the client (if they were sent).
        await TrySendPendingUpdates(connectedClient.Groups, cancellationToken).ConfigureAwait(false);

        while (_connectedClients.IsClientConnected(connectedClient.ClientId))
        {
            MessageMetadata?metadata = null;

            try
            {
                var message = await connection.ReceiveAsync(cancellationToken).ConfigureAwait(false);

                if (message is not MessageWithMetadata messageWithMetadata)
                {
                    throw new InvalidOperationException($"Message is not of {typeof(MessageWithMetadata).Name} type.");
                }

                metadata = messageWithMetadata.Metadata;

                if (messageWithMetadata.Metadata?.MessageId != null && idempotencyKeys.ContainsKey(messageWithMetadata.Metadata.MessageId))
                {
                    _logger.LogDebug(
                        "Message with id {MessageId} has already been handled. Skipping duplicate (idempotency).",
                        messageWithMetadata.Metadata.MessageId);
                    continue;
                }

                await DispatchMessageAsync(connectedClient, messageWithMetadata.Message, cancellationToken).ConfigureAwait(false);

                // TODO: Unit test this.
                if (messageWithMetadata.Metadata?.ResponseMessageTypeId != null)
                {
                    // TODO: Send query response in background, do not block connection handling.
                    var responseType = _messageTypeCache.GetTypeById(messageWithMetadata.Metadata.ResponseMessageTypeId);
                    var response     = await _queryDispatcher.DispatchAsync(connectedClient, messageWithMetadata.Message, responseType, cancellationToken)
                                       .ConfigureAwait(false);

                    // TODO: Do this also in background.
                    await connectedClient.Connection.SendAsync(response, new MessageMetadata
                    {
                        MessageId = messageWithMetadata.Metadata.MessageId
                    }, cancellationToken)
                    .ConfigureAwait(false);
                }

                // If everything was dispatched successfully:
                if (messageWithMetadata.Metadata?.MessageId != null && !idempotencyKeys.ContainsKey(messageWithMetadata.Metadata.MessageId))
                {
                    idempotencyKeys.Add(messageWithMetadata.Metadata.MessageId, DateTime.UtcNow);
                }

                foreach (var item in idempotencyKeys.Where(x => x.Value < DateTime.UtcNow - TimeSpan.FromMinutes(1)))
                {
                    idempotencyKeys.Remove(item.Key);
                }

                if (messageWithMetadata.Metadata?.AcknowledgementType == AcknowledgementType.Handled && messageWithMetadata.Metadata.MessageId != null)
                {
                    var serverToClientMetadata = new MessageMetadata
                    {
                        MessageId = messageWithMetadata.Metadata.MessageId
                    };

                    await connectedClient.Connection.SendAsync(new AcknowledgeHandled(messageWithMetadata.Metadata.MessageId), serverToClientMetadata, cancellationToken)
                    .ConfigureAwait(false);
                }
            }
            catch
            {
                _connectedClients.Remove(connectedClient.ClientId);
                throw; // If you delete this line and have ncrunch, your PC will die.
            }
            finally
            {
                var groups = connectedClient.Groups;

                // TODO: Unit test this (affected groups).
                if ((metadata as ClientToServerMessageMetadata)?.AffectedGroups != null)
                {
                    groups = groups.Where(group => (metadata as ClientToServerMessageMetadata)?.AffectedGroups?.Contains(group) ?? false);
                }

                // TODO: Do not empty groups if we came here from catch - when client is disconnected we need to notify everyone.
                if (metadata == null || !metadata.SendUpdate)
                {
                    groups = Enumerable.Empty <string>();
                }

                await TrySendPendingUpdates(groups, cancellationToken).ConfigureAwait(false);
            }
        }
    }