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); } } }
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); } } }