public async Task ServiceConnectionWithOfflinePingWillTriggerDisconnectClients() { using (StartVerifiableLog(out var loggerFactory, LogLevel.Debug)) { var hubConfig = Utility.GetActualHubConfig(loggerFactory); var appName = "app1"; var hub = "chat"; var scm = new TestServiceConnectionHandler(); hubConfig.Resolver.Register(typeof(IServiceConnectionManager), () => scm); var ccm = new ClientConnectionManager(hubConfig, loggerFactory); hubConfig.Resolver.Register(typeof(IClientConnectionManager), () => ccm); DispatcherHelper.PrepareAndGetDispatcher(new TestAppBuilder(), hubConfig, new ServiceOptions { ConnectionString = ConnectionString }, appName, loggerFactory); using (var proxy = new TestServiceConnectionProxy(ccm, loggerFactory: loggerFactory)) { // prepare 2 clients with different instancesId connected var instanceId1 = Guid.NewGuid().ToString(); var connectionId1 = Guid.NewGuid().ToString("N"); var header1 = new Dictionary <string, StringValues>() { { Constants.AsrsInstanceId, instanceId1 } }; var instanceId2 = Guid.NewGuid().ToString(); var connectionId2 = Guid.NewGuid().ToString("N"); var header2 = new Dictionary <string, StringValues>() { { Constants.AsrsInstanceId, instanceId2 } }; // start the server connection await proxy.StartServiceAsync().OrTimeout(); // Application layer sends OpenConnectionMessage for client1 var connectTask = scm.WaitForTransportOutputMessageAsync(typeof(GroupBroadcastDataMessage)).OrTimeout(); var openConnectionMessage = new OpenConnectionMessage(connectionId1, new Claim[0], header1, $"?transport=webSockets&connectionToken=conn1&connectionData=%5B%7B%22name%22%3A%22{hub}%22%7D%5D"); await proxy.WriteMessageAsync(openConnectionMessage); // client1 is connected var connectMessage = (await connectTask)as GroupBroadcastDataMessage; Assert.NotNull(connectMessage); Assert.Equal($"hg-{hub}.note", connectMessage.GroupName); var message = connectMessage.Payloads["json"].GetJsonMessageFromSingleFramePayload <HubResponseItem>(); Assert.Equal("Connected", message.A[0]); ccm.ClientConnections.TryGetValue(connectionId1, out var transport1); Assert.NotNull(transport1); // Application layer sends OpenConnectionMessage for client2 connectTask = scm.WaitForTransportOutputMessageAsync(typeof(GroupBroadcastDataMessage)).OrTimeout(); openConnectionMessage = new OpenConnectionMessage(connectionId2, new Claim[0], header2, $"?transport=webSockets&connectionToken=conn2&connectionData=%5B%7B%22name%22%3A%22{hub}%22%7D%5D"); await proxy.WriteMessageAsync(openConnectionMessage); // client2 is connected connectMessage = (await connectTask)as GroupBroadcastDataMessage; Assert.NotNull(connectMessage); Assert.Equal($"hg-{hub}.note", connectMessage.GroupName); message = connectMessage.Payloads["json"].GetJsonMessageFromSingleFramePayload <HubResponseItem>(); Assert.Equal("Connected", message.A[0]); ccm.ClientConnections.TryGetValue(connectionId2, out var transport2); Assert.NotNull(transport2); // Send ServerOfflinePing on instance1 and will trigger cleanup related client1 connectTask = scm.WaitForTransportOutputMessageAsync(typeof(GroupBroadcastDataMessage)).OrTimeout(); await proxy.WriteMessageAsync(new PingMessage() { Messages = new[] { "offline", instanceId1 } }); // Validate client1 disconnect connectMessage = (await connectTask)as GroupBroadcastDataMessage; Assert.NotNull(connectMessage); Assert.Equal($"hg-{hub}.note", connectMessage.GroupName); message = connectMessage.Payloads["json"] .GetJsonMessageFromSingleFramePayload <HubResponseItem>(); Assert.Equal("Disconnected", message.A[0]); // Validate client2 is still connected Assert.Single(ccm.ClientConnections); Assert.Equal(connectionId2, ccm.ClientConnections.FirstOrDefault().Key); } } }
public async Task ServiceConnectionWithTransportLayerClosedShouldCleanupEndlessInvokeClientConnections() { using (StartVerifiableLog(out var loggerFactory, LogLevel.Debug, expectedErrors: c => true)) { var hubConfig = Utility.GetActualHubConfig(loggerFactory); var appName = "app1"; var hub = "EndlessInvoke"; var scm = new TestServiceConnectionHandler(); hubConfig.Resolver.Register(typeof(IServiceConnectionManager), () => scm); var ccm = new ClientConnectionManager(hubConfig, loggerFactory); hubConfig.Resolver.Register(typeof(IClientConnectionManager), () => ccm); DispatcherHelper.PrepareAndGetDispatcher(new TestAppBuilder(), hubConfig, new ServiceOptions { ConnectionString = ConnectionString }, appName, loggerFactory); using (var proxy = new TestServiceConnectionProxy(ccm, loggerFactory)) { // start the server connection var connectionTask = proxy.StartAsync(); await proxy.ConnectionInitializedTask.OrTimeout(); var clientConnection = Guid.NewGuid().ToString("N"); var connectTask = scm.WaitForTransportOutputMessageAsync(typeof(GroupBroadcastDataMessage)) .OrTimeout(); // Application layer sends OpenConnectionMessage var openConnectionMessage = new OpenConnectionMessage(clientConnection, new Claim[0], null, $"?transport=webSockets&connectionToken=conn1&connectionData=%5B%7B%22name%22%3A%22{hub}%22%7D%5D"); await proxy.WriteMessageAsync(openConnectionMessage); var connectMessage = (await connectTask)as GroupBroadcastDataMessage; Assert.NotNull(connectMessage); Assert.Equal($"hg-{hub}.note", connectMessage.GroupName); var message = connectMessage.Payloads["json"] .GetJsonMessageFromSingleFramePayload <HubResponseItem>(); Assert.Equal("Connected", message.A[0]); // group message goes into the manager // make sure the tcs is called before writing message var gbTask = scm.WaitForTransportOutputMessageAsync(typeof(GroupBroadcastDataMessage)).OrTimeout(); await proxy.WriteMessageAsync(new ConnectionDataMessage(clientConnection, Encoding.UTF8.GetBytes($"{{\"H\":\"{hub}\",\"M\":\"JoinGroup\",\"A\":[\"user1\",\"group1\"],\"I\":1}}"))); var broadcastMessage = (await gbTask)as GroupBroadcastDataMessage; Assert.NotNull(broadcastMessage); Assert.Equal($"hg-{hub}.group1", broadcastMessage.GroupName); // close transport layer proxy.TestConnectionContext.Application.Output.Complete(); await connectionTask.OrTimeout(); await proxy.WaitForConnectionClose.OrTimeout(); Assert.Equal(ServiceConnectionStatus.Disconnected, proxy.Status); // cleaned up clearly Assert.Empty(ccm.ClientConnections); } } }