public void SendToAllButCaller() { using (var host = new MemoryHost()) { host.MapConnection <FilteredConnection>("/filter"); var connection1 = new Client.Connection("http://foo/filter"); var connection2 = new Client.Connection("http://foo/filter"); var wh1 = new ManualResetEventSlim(initialState: false); var wh2 = new ManualResetEventSlim(initialState: false); connection1.Received += data => wh1.Set(); connection2.Received += data => wh2.Set(); connection1.Start(host).Wait(); connection2.Start(host).Wait(); connection1.Send("test").Wait(); Assert.False(wh1.WaitHandle.WaitOne(TimeSpan.FromSeconds(5))); Assert.True(wh2.WaitHandle.WaitOne(TimeSpan.FromSeconds(5))); connection1.Stop(); connection2.Stop(); } }
public void SendToAllButCaller(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(); var connection1 = new Client.Connection(host.Url + "/filter"); var connection2 = new Client.Connection(host.Url + "/filter"); var wh1 = new ManualResetEventSlim(initialState: false); var wh2 = new ManualResetEventSlim(initialState: false); connection1.Received += data => wh1.Set(); connection2.Received += data => wh2.Set(); connection1.Start(host.Transport).Wait(); connection2.Start(host.Transport).Wait(); connection1.SendWithTimeout("test"); Assert.False(wh1.WaitHandle.WaitOne(TimeSpan.FromSeconds(5))); Assert.True(wh2.WaitHandle.WaitOne(TimeSpan.FromSeconds(5))); connection1.Stop(); connection2.Stop(); } }
public void SendCanBeCalledAfterStateChangedEvent(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(); var connection = new Client.Connection(host.Url + "/multisend"); var results = new List <string>(); connection.Received += data => { results.Add(data); }; connection.StateChanged += stateChange => { if (stateChange.NewState == Client.ConnectionState.Connected) { connection.SendWithTimeout(""); } }; connection.Start(host.Transport).Wait(); Thread.Sleep(TimeSpan.FromSeconds(5)); connection.Stop(); Debug.WriteLine(String.Join(", ", results)); Assert.Equal(4, results.Count); } }
public void GroupsReceiveMessages(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(); var connection = new Client.Connection(host.Url + "/groups"); var list = new List <string>(); connection.Received += data => { list.Add(data); }; connection.Start(host.Transport).Wait(); // Join the group connection.SendWithTimeout(new { type = 1, group = "test" }); // Sent a message connection.SendWithTimeout(new { type = 3, group = "test", message = "hello to group test" }); // Leave the group connection.SendWithTimeout(new { type = 2, group = "test" }); // Send a message connection.SendWithTimeout(new { type = 3, group = "test", message = "goodbye to group test" }); Thread.Sleep(TimeSpan.FromSeconds(5)); connection.Stop(); Assert.Equal(1, list.Count); Assert.Equal("hello to group test", list[0]); } }
public void SendRaisesOnReceivedFromAllEvents() { using (var host = new MemoryHost()) { host.MapConnection <MySendingConnection>("/multisend"); var connection = new Client.Connection("http://foo/multisend"); var results = new List <string>(); connection.Received += data => { results.Add(data); }; connection.Start(host).Wait(); connection.Send("").Wait(); Thread.Sleep(TimeSpan.FromSeconds(5)); connection.Stop(); Debug.WriteLine(String.Join(", ", results)); Assert.Equal(4, results.Count); Assert.Equal("OnConnectedAsync1", results[0]); Assert.Equal("OnConnectedAsync2", results[1]); Assert.Equal("OnReceivedAsync1", results[2]); Assert.Equal("OnReceivedAsync2", results[3]); } }
// [InlineData(HostType.IIS, TransportType.LongPolling)] public void ThrownWebExceptionShouldBeUnwrapped(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(); var connection = new Client.Connection(host.Url + "/ErrorsAreFun"); // Expecting 404 var aggEx = Assert.Throws <AggregateException>(() => connection.Start(host.Transport).Wait()); connection.Stop(); using (var ser = aggEx.GetError()) { if (hostType == HostType.IISExpress) { Assert.Equal(System.Net.HttpStatusCode.InternalServerError, ser.StatusCode); } else { Assert.Equal(System.Net.HttpStatusCode.NotFound, ser.StatusCode); } Assert.NotNull(ser.ResponseBody); Assert.NotNull(ser.Exception); } } }
public void SendRaisesOnReceivedFromAllEvents(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(); var connection = new Client.Connection(host.Url + "/multisend"); var results = new List <string>(); connection.Received += data => { results.Add(data); }; connection.Start(host.Transport).Wait(); connection.SendWithTimeout(""); Thread.Sleep(TimeSpan.FromSeconds(5)); connection.Stop(); Debug.WriteLine(String.Join(", ", results)); Assert.Equal(4, results.Count); Assert.Equal("OnConnectedAsync1", results[0]); Assert.Equal("OnConnectedAsync2", results[1]); Assert.Equal("OnReceivedAsync1", results[2]); Assert.Equal("OnReceivedAsync2", results[3]); } }
public void GroupsReceiveMessages(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(); var connection = new Client.Connection(host.Url + "/groups"); var list = new List<string>(); connection.Received += data => { list.Add(data); }; connection.Start(host.Transport).Wait(); // Join the group connection.SendWithTimeout(new { type = 1, group = "test" }); // Sent a message connection.SendWithTimeout(new { type = 3, group = "test", message = "hello to group test" }); // Leave the group connection.SendWithTimeout(new { type = 2, group = "test" }); // Send a message connection.SendWithTimeout(new { type = 3, group = "test", message = "goodbye to group test" }); Thread.Sleep(TimeSpan.FromSeconds(5)); connection.Stop(); Assert.Equal(1, list.Count); Assert.Equal("hello to group test", list[0]); } }
public void ReconnectDoesntFireAfterTimeOut(TransportType transportType, MessageBusType messageBusType) { using (var host = new MemoryHost()) { var conn = new MyReconnect(); host.Configure(app => { var config = new ConnectionConfiguration { Resolver = new DefaultDependencyResolver() }; UseMessageBus(messageBusType, config.Resolver); app.MapSignalR <MyReconnect>("/endpoint", config); var configuration = config.Resolver.Resolve <IConfigurationManager>(); configuration.DisconnectTimeout = TimeSpan.FromSeconds(6); configuration.ConnectionTimeout = TimeSpan.FromSeconds(2); configuration.KeepAlive = null; config.Resolver.Register(typeof(MyReconnect), () => conn); }); var connection = new Client.Connection("http://foo/endpoint"); var transport = CreateTransport(transportType, host); connection.Start(transport).Wait(); Thread.Sleep(TimeSpan.FromSeconds(5)); connection.Stop(); Assert.Equal(0, conn.Reconnects); } }
public void ThrownWebExceptionShouldBeUnwrapped(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(); var connection = new Client.Connection(host.Url + "/ErrorsAreFun"); // Expecting 404 var aggEx = Assert.Throws<AggregateException>(() => connection.Start(host.Transport).Wait()); connection.Stop(); using (var ser = aggEx.GetError()) { if (hostType == HostType.IISExpress) { Assert.Equal(System.Net.HttpStatusCode.InternalServerError, ser.StatusCode); } else { Assert.Equal(System.Net.HttpStatusCode.NotFound, ser.StatusCode); } Assert.NotNull(ser.ResponseBody); Assert.NotNull(ser.Exception); } } }
public void GroupsReceiveMessages() { using (var host = new MemoryHost()) { host.MapConnection <MyGroupConnection>("/groups"); var connection = new Client.Connection("http://foo/groups"); var list = new List <string>(); connection.Received += data => { list.Add(data); }; connection.Start(host).Wait(); // Join the group connection.Send(new { type = 1, group = "test" }).Wait(); // Sent a message connection.Send(new { type = 3, group = "test", message = "hello to group test" }).Wait(); // Leave the group connection.Send(new { type = 2, group = "test" }).Wait(); // Send a message connection.Send(new { type = 3, group = "test", message = "goodbye to group test" }).Wait(); Thread.Sleep(TimeSpan.FromSeconds(5)); connection.Stop(); Assert.Equal(1, list.Count); Assert.Equal("hello to group test", list[0]); } }
public void SendCanBeCalledAfterStateChangedEvent() { using (var host = new MemoryHost()) { host.MapConnection <MySendingConnection>("/multisend"); var connection = new Client.Connection("http://foo/multisend"); var results = new List <string>(); connection.Received += data => { results.Add(data); }; connection.StateChanged += stateChange => { if (stateChange.NewState == Client.ConnectionState.Connected) { connection.Send("").Wait(); } }; connection.Start(host).Wait(); Thread.Sleep(TimeSpan.FromSeconds(5)); connection.Stop(); Debug.WriteLine(String.Join(", ", results)); Assert.Equal(4, results.Count); } }
public static IDisposable ClientGroupsSyncWithServerGroupsOnReconnectLongPolling() { var host = new MemoryHost(); host.Configuration.KeepAlive = null; host.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(5); host.Configuration.HeartbeatInterval = TimeSpan.FromSeconds(2); host.MapConnection<MyRejoinGroupConnection>("/groups"); var connection = new Client.Connection("http://foo/groups"); var inGroupOnReconnect = new List<bool>(); var wh = new ManualResetEventSlim(); connection.Received += message => { Console.WriteLine(message); wh.Set(); }; connection.Reconnected += () => { var inGroup = connection.Groups.Contains(typeof(MyRejoinGroupConnection).FullName + ".test"); if (!inGroup) { Debugger.Break(); } inGroupOnReconnect.Add(inGroup); connection.Send(new { type = 3, group = "test", message = "Reconnected" }).Wait(); }; connection.Start(new Client.Transports.LongPollingTransport(host)).Wait(); // Join the group connection.Send(new { type = 1, group = "test" }).Wait(); Thread.Sleep(TimeSpan.FromSeconds(10)); if (!wh.Wait(TimeSpan.FromSeconds(10))) { Debugger.Break(); } Console.WriteLine(inGroupOnReconnect.Count > 0); Console.WriteLine(String.Join(", ", inGroupOnReconnect.Select(b => b.ToString()))); connection.Stop(); return host; }
public static IDisposable ClientGroupsSyncWithServerGroupsOnReconnectLongPolling() { var host = new MemoryHost(); host.Configure(app => { var config = new ConnectionConfiguration() { Resolver = new DefaultDependencyResolver() }; app.MapConnection <MyRejoinGroupConnection>("/groups", config); var configuration = config.Resolver.Resolve <IConfigurationManager>(); configuration.KeepAlive = null; configuration.ConnectionTimeout = TimeSpan.FromSeconds(1); }); var connection = new Client.Connection("http://foo/groups"); var inGroupOnReconnect = new List <bool>(); var wh = new ManualResetEventSlim(); connection.Received += message => { Console.WriteLine(message); wh.Set(); }; connection.Reconnected += () => { connection.Send(new { type = 3, group = "test", message = "Reconnected" }).Wait(); }; connection.Start(new Client.Transports.LongPollingTransport(host)).Wait(); // Join the group connection.Send(new { type = 1, group = "test" }).Wait(); Thread.Sleep(TimeSpan.FromSeconds(10)); if (!wh.Wait(TimeSpan.FromSeconds(10))) { Debugger.Break(); } Console.WriteLine(inGroupOnReconnect.Count > 0); Console.WriteLine(String.Join(", ", inGroupOnReconnect.Select(b => b.ToString()))); connection.Stop(); return(host); }
public static IDisposable ClientGroupsSyncWithServerGroupsOnReconnectLongPolling() { var host = new MemoryHost(); host.Configuration.KeepAlive = null; host.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(5); host.Configuration.HeartbeatInterval = TimeSpan.FromSeconds(2); host.MapConnection <MyRejoinGroupConnection>("/groups"); var connection = new Client.Connection("http://foo/groups"); var inGroupOnReconnect = new List <bool>(); var wh = new ManualResetEventSlim(); connection.Received += message => { Console.WriteLine(message); wh.Set(); }; connection.Reconnected += () => { var inGroup = connection.Groups.Contains(typeof(MyRejoinGroupConnection).FullName + ".test"); if (!inGroup) { Debugger.Break(); } inGroupOnReconnect.Add(inGroup); connection.Send(new { type = 3, group = "test", message = "Reconnected" }).Wait(); }; connection.Start(new Client.Transports.LongPollingTransport(host)).Wait(); // Join the group connection.Send(new { type = 1, group = "test" }).Wait(); Thread.Sleep(TimeSpan.FromSeconds(10)); if (!wh.Wait(TimeSpan.FromSeconds(10))) { Debugger.Break(); } Console.WriteLine(inGroupOnReconnect.Count > 0); Console.WriteLine(String.Join(", ", inGroupOnReconnect.Select(b => b.ToString()))); connection.Stop(); return(host); }
public void SendWithSyncErrorThrows(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(); var connection = new Client.Connection(host.Url + "/sync-error"); connection.Start(host.Transport).Wait(); Assert.Throws <AggregateException>(() => connection.SendWithTimeout("test")); connection.Stop(); } }
public void EnvironmentIsAvailable(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(); var connection = new Client.Connection(host.Url + "/items"); var connection2 = new Client.Connection(host.Url + "/items"); var results = new List <RequestItemsResponse>(); connection2.Received += data => { var val = JsonConvert.DeserializeObject <RequestItemsResponse>(data); if (!results.Contains(val)) { results.Add(val); } }; connection.Start(host.Transport).Wait(); connection2.Start(host.Transport).Wait(); Thread.Sleep(TimeSpan.FromSeconds(2)); connection.SendWithTimeout(null); Thread.Sleep(TimeSpan.FromSeconds(2)); connection.Stop(); Thread.Sleep(TimeSpan.FromSeconds(2)); Debug.WriteLine(String.Join(", ", results)); Assert.Equal(3, results.Count); Assert.Equal("OnConnectedAsync", results[0].Method); Assert.Equal(1, results[0].Keys.Length); Assert.Equal("owin.environment", results[0].Keys[0]); Assert.Equal("OnReceivedAsync", results[1].Method); Assert.Equal(1, results[1].Keys.Length); Assert.Equal("owin.environment", results[1].Keys[0]); Assert.Equal("OnDisconnectAsync", results[2].Method); Assert.Equal(1, results[2].Keys.Length); Assert.Equal("owin.environment", results[2].Keys[0]); connection2.Stop(); } }
// [InlineData(HostType.IIS, TransportType.LongPolling)] public void ReconnectFiresAfterHostShutDown(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(); var connection = new Client.Connection(host.Url + "/my-reconnect"); connection.Start(host.Transport).Wait(); host.Shutdown(); Thread.Sleep(TimeSpan.FromSeconds(5)); Assert.Equal(Client.ConnectionState.Reconnecting, connection.State); connection.Stop(); } }
public void FarmDisconnectOnlyRaisesEventOnce() { // Each node shares the same bus but are indepenent servers var counters = new SignalR.Infrastructure.PerformanceCounterManager(); var configurationManager = new DefaultConfigurationManager(); using (var bus = new MessageBus(new StringMinifier(), new TraceManager(), counters, configurationManager)) { var nodeCount = 3; var nodes = new List <ServerNode>(); for (int i = 0; i < nodeCount; i++) { nodes.Add(new ServerNode(bus)); } var timeout = TimeSpan.FromSeconds(5); foreach (var node in nodes) { node.Server.Configuration.HeartBeatInterval = timeout; node.Server.Configuration.DisconnectTimeout = TimeSpan.Zero; node.Server.MapConnection <FarmConnection>("/echo"); } var loadBalancer = new LoadBalancer(nodes.Select(f => f.Server).ToArray()); var transport = new Client.Transports.LongPollingTransport(loadBalancer); var connection = new Client.Connection("http://goo/echo"); connection.Start(transport).Wait(); for (int i = 0; i < nodes.Count; i++) { nodes[i].Broadcast(String.Format("From Node {0}: {1}", i, i + 1)); Thread.Sleep(TimeSpan.FromSeconds(1)); } connection.Stop(); Thread.Sleep(TimeSpan.FromTicks(timeout.Ticks * nodes.Count)); Assert.Equal(1, nodes.Sum(n => n.Connection.DisconnectCount)); } }
public void ThrownWebExceptionShouldBeUnwrapped() { var host = new MemoryHost(); host.MapConnection<MyBadConnection>("/ErrorsAreFun"); var connection = new Client.Connection("http://test/ErrorsAreFun"); // Expecting 404 var aggEx = Assert.Throws<AggregateException>(() => connection.Start(host).Wait()); connection.Stop(); using (var ser = aggEx.GetError()) { Assert.Equal(ser.StatusCode, HttpStatusCode.NotFound); Assert.NotNull(ser.ResponseBody); Assert.NotNull(ser.Exception); } }
public void ReconnectFiresAfterHostShutDown() { using (var host = new MemoryHost()) { var conn = new MyReconnect(); host.DependencyResolver.Register(typeof(MyReconnect), () => conn); host.MapConnection <MyReconnect>("/endpoint"); var connection = new Client.Connection("http://foo/endpoint"); connection.Start(host).Wait(); host.Dispose(); Thread.Sleep(TimeSpan.FromSeconds(5)); Assert.Equal(Client.ConnectionState.Reconnecting, connection.State); connection.Stop(); } }
public void GroupsAreNotReadOnConnectedAsync(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(); var connection = new Client.Connection(host.Url + "/group-echo"); ((Client.IConnection)connection).Groups.Add(typeof(MyGroupEchoConnection).FullName + ".test"); connection.Received += data => { Assert.False(true, "Unexpectedly received data"); }; connection.Start(host.Transport).Wait(); Thread.Sleep(TimeSpan.FromSeconds(5)); connection.Stop(); } }
public void ThrownWebExceptionShouldBeUnwrapped() { var host = new MemoryHost(); host.MapConnection <MyBadConnection>("/ErrorsAreFun"); var connection = new Client.Connection("http://test/ErrorsAreFun"); // Expecting 404 var aggEx = Assert.Throws <AggregateException>(() => connection.Start(host).Wait()); connection.Stop(); using (var ser = aggEx.GetError()) { Assert.Equal(ser.StatusCode, HttpStatusCode.NotFound); Assert.NotNull(ser.ResponseBody); Assert.NotNull(ser.Exception); } }
public void ClientGroupsSyncWithServerGroupsOnReconnectWhenNotRejoiningGroups(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(keepAlive: null, connectionTimeout: 5, hearbeatInterval: 2); var connection = new Client.Connection(host.Url + "/groups"); var inGroupOnReconnect = new List <bool>(); var wh = new ManualResetEventSlim(); connection.Received += message => { Assert.Equal("Reconnected", message); inGroupOnReconnect.Add(!connection.Groups.Contains(typeof(MyGroupConnection).FullName + ".test")); inGroupOnReconnect.Add(connection.Groups.Contains(typeof(MyGroupConnection).FullName + ".test2")); wh.Set(); }; connection.Reconnected += () => { connection.SendWithTimeout(new { type = 1, group = "test2" }); connection.SendWithTimeout(new { type = 3, group = "test2", message = "Reconnected" }); }; connection.Start(host.Transport).Wait(); // Join the group connection.SendWithTimeout(new { type = 1, group = "test" }); // Force reconnect Thread.Sleep(TimeSpan.FromSeconds(5)); Assert.True(wh.Wait(TimeSpan.FromSeconds(5)), "Client didn't receive message sent to test group."); Assert.True(inGroupOnReconnect.Count > 0); Assert.True(inGroupOnReconnect.All(b => b)); connection.Stop(); } }
public void ReconnectFiresAfterTimeOutLongPolling() { using (var host = new MemoryHost()) { var conn = new MyReconnect(); host.Configuration.KeepAlive = null; host.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(5); host.Configuration.HeartBeatInterval = TimeSpan.FromSeconds(5); host.DependencyResolver.Register(typeof(MyReconnect), () => conn); host.MapConnection <MyReconnect>("/endpoint"); var connection = new Client.Connection("http://foo/endpoint"); connection.Start(new Client.Transports.LongPollingTransport(host)).Wait(); Thread.Sleep(TimeSpan.FromSeconds(15)); connection.Stop(); Assert.InRange(conn.Reconnects, 1, 4); } }
public void ClientGroupsSyncWithServerGroupsOnReconnect(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(keepAlive: 0, connectionTimeout: 5, hearbeatInterval: 2); var connection = new Client.Connection(host.Url + "/rejoin-groups"); var inGroupOnReconnect = new List<bool>(); var wh = new ManualResetEventSlim(); connection.Received += message => { Assert.Equal("Reconnected", message); wh.Set(); }; connection.Reconnected += () => { inGroupOnReconnect.Add(connection.Groups.Contains(typeof(MyRejoinGroupsConnection).FullName + ".test")); connection.SendWithTimeout(new { type = 3, group = "test", message = "Reconnected" }); }; connection.Start(host.Transport).Wait(); // Join the group connection.SendWithTimeout(new { type = 1, group = "test" }); // Force reconnect Thread.Sleep(TimeSpan.FromSeconds(10)); Assert.True(wh.Wait(TimeSpan.FromSeconds(5)), "Client didn't receive message sent to test group."); Assert.True(inGroupOnReconnect.Count > 0); Assert.True(inGroupOnReconnect.All(b => b)); connection.Stop(); } }
public void GroupsAreNotReadOnConnectedAsync() { using (var host = new MemoryHost()) { host.MapConnection <MyConnection>("/echo"); var connection = new Client.Connection("http://foo/echo"); connection.Groups = new List <string> { typeof(MyConnection).FullName + ".test" }; connection.Received += data => { Assert.False(true, "Unexpectedly received data"); }; connection.Start(host).Wait(); Thread.Sleep(TimeSpan.FromSeconds(5)); connection.Stop(); } }
public void DisconnectFiresForPersistentConnectionWhenClientGoesAway() { var host = new MemoryHost(); host.MapConnection<MyConnection>("/echo"); host.Configuration.DisconnectTimeout = TimeSpan.Zero; host.Configuration.HeartBeatInterval = TimeSpan.FromSeconds(5); var connectWh = new ManualResetEventSlim(); var disconnectWh = new ManualResetEventSlim(); host.DependencyResolver.Register(typeof(MyConnection), () => new MyConnection(connectWh, disconnectWh)); var connection = new Client.Connection("http://foo/echo"); // Maximum wait time for disconnect to fire (3 heart beat intervals) var disconnectWait = TimeSpan.FromTicks(host.Configuration.HeartBeatInterval.Ticks * 3); connection.Start(host).Wait(); Assert.True(connectWh.Wait(TimeSpan.FromSeconds(10)), "Connect never fired"); connection.Stop(); Assert.True(disconnectWh.Wait(disconnectWait), "Disconnect never fired"); }
public void GroupsRejoinedWhenOnRejoiningGroupsOverridden(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(keepAlive: null, connectionTimeout: 2, hearbeatInterval: 2); var connection = new Client.Connection(host.Url + "/rejoin-groups"); var list = new List <string>(); connection.Received += data => { list.Add(data); }; connection.Start(host.Transport).Wait(); // Join the group connection.SendWithTimeout(new { type = 1, group = "test" }); // Sent a message connection.SendWithTimeout(new { type = 3, group = "test", message = "hello to group test" }); // Force Reconnect Thread.Sleep(TimeSpan.FromSeconds(5)); // Send a message connection.SendWithTimeout(new { type = 3, group = "test", message = "goodbye to group test" }); Thread.Sleep(TimeSpan.FromSeconds(5)); connection.Stop(); Assert.Equal(2, list.Count); Assert.Equal("hello to group test", list[0]); Assert.Equal("goodbye to group test", list[1]); } }
public void GroupsRejoinedWhenOnRejoiningGroupsOverridden() { using (var host = new MemoryHost()) { host.Configuration.KeepAlive = null; host.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(2); host.Configuration.HeartBeatInterval = TimeSpan.FromSeconds(2); host.MapConnection <MyRejoinGroupConnection>("/groups"); var connection = new Client.Connection("http://foo/groups"); var list = new List <string>(); connection.Received += data => { list.Add(data); }; connection.Start(host).Wait(); // Join the group connection.Send(new { type = 1, group = "test" }).Wait(); // Sent a message connection.Send(new { type = 3, group = "test", message = "hello to group test" }).Wait(); // Force Reconnect Thread.Sleep(TimeSpan.FromSeconds(5)); // Send a message connection.Send(new { type = 3, group = "test", message = "goodbye to group test" }).Wait(); Thread.Sleep(TimeSpan.FromSeconds(5)); connection.Stop(); Assert.Equal(2, list.Count); Assert.Equal("hello to group test", list[0]); Assert.Equal("goodbye to group test", list[1]); } }
private static void BroadcastFive(MemoryHost host) { var connection = new Client.Connection("http://samples/Raw-connection"); connection.Error += e => { Console.Error.WriteLine("========ERROR=========="); Console.Error.WriteLine(e.GetBaseException().ToString()); Console.Error.WriteLine("======================="); }; connection.Start(new Client.Transports.ServerSentEventsTransport(host)).Wait(); try { for (int i = 0; i < 5; i++) { var payload = new { type = MessageType.Broadcast, value = "message " + i.ToString() }; connection.Send(payload).Wait(); } } catch (Exception ex) { Console.Error.WriteLine("========ERROR=========="); Console.Error.WriteLine(ex.GetBaseException().ToString()); Console.Error.WriteLine("======================="); } finally { connection.Stop(); } }
public void DisconnectFiresForPersistentConnectionWhenClientGoesAway() { using (var host = new MemoryHost()) { host.MapConnection <MyConnection>("/echo"); host.Configuration.DisconnectTimeout = TimeSpan.Zero; host.Configuration.HeartBeatInterval = TimeSpan.FromSeconds(5); var connectWh = new ManualResetEventSlim(); var disconnectWh = new ManualResetEventSlim(); host.DependencyResolver.Register(typeof(MyConnection), () => new MyConnection(connectWh, disconnectWh)); var connection = new Client.Connection("http://foo/echo"); // Maximum wait time for disconnect to fire (3 heart beat intervals) var disconnectWait = TimeSpan.FromTicks(host.Configuration.HeartBeatInterval.Ticks * 3); connection.Start(host).Wait(); Assert.True(connectWh.Wait(TimeSpan.FromSeconds(10)), "Connect never fired"); connection.Stop(); Assert.True(disconnectWh.Wait(disconnectWait), "Disconnect never fired"); } }
public async Task DisconnectFiresForPersistentConnectionWhenClientCallsStop() { using (var host = new MemoryHost()) { var connectWh = new TaskCompletionSource <object>(); var disconnectWh = new TaskCompletionSource <object>(); var dr = new DefaultDependencyResolver(); var configuration = dr.Resolve <IConfigurationManager>(); host.Configure(app => { var config = new ConnectionConfiguration { Resolver = dr }; app.MapSignalR <MyConnection>("/echo", config); configuration.DisconnectTimeout = TimeSpan.FromSeconds(6); dr.Register(typeof(MyConnection), () => new MyConnection(connectWh, disconnectWh)); }); var connection = new Client.Connection("http://foo/echo"); // Maximum wait time for disconnect to fire (3 heart beat intervals) var disconnectWait = TimeSpan.FromTicks(configuration.HeartbeatInterval().Ticks * 3); await connection.Start(host); await connectWh.Task.OrTimeout(TimeSpan.FromSeconds(10)); connection.Stop(); await disconnectWh.Task.OrTimeout(disconnectWait); } }
public void DisconnectFiresForPersistentConnectionWhenClientGoesAway() { using (var host = new MemoryHost()) { var connectWh = new ManualResetEventSlim(); var disconnectWh = new ManualResetEventSlim(); var dr = new DefaultDependencyResolver(); var configuration = dr.Resolve <IConfigurationManager>(); host.Configure(app => { var config = new ConnectionConfiguration { Resolver = dr }; app.MapSignalR <MyConnection>("/echo", config); configuration.DisconnectTimeout = TimeSpan.FromSeconds(6); dr.Register(typeof(MyConnection), () => new MyConnection(connectWh, disconnectWh)); }); var connection = new Client.Connection("http://foo/echo"); // Maximum wait time for disconnect to fire (3 heart beat intervals) var disconnectWait = TimeSpan.FromTicks(configuration.HeartbeatInterval().Ticks * 3); connection.Start(host).Wait(); Assert.True(connectWh.Wait(TimeSpan.FromSeconds(10)), "Connect never fired"); connection.Stop(); Assert.True(disconnectWh.Wait(disconnectWait), "Disconnect never fired"); } }
public void SendToGroupFromOutsideOfConnection() { using (var host = new MemoryHost()) { IPersistentConnectionContext connectionContext = null; host.Configure(app => { var configuration = new ConnectionConfiguration { Resolver = new DefaultDependencyResolver() }; app.MapConnection <FilteredConnection>("/echo", configuration); connectionContext = configuration.Resolver.Resolve <IConnectionManager>().GetConnectionContext <FilteredConnection>(); }); var connection1 = new Client.Connection("http://foo/echo"); var wh1 = new ManualResetEventSlim(initialState: false); connection1.Start(host).Wait(); connection1.Received += data => { Assert.Equal("yay", data); wh1.Set(); }; connectionContext.Groups.Add(connection1.ConnectionId, "Foo").Wait(); connectionContext.Groups.Send("Foo", "yay"); Assert.True(wh1.Wait(TimeSpan.FromSeconds(10))); connection1.Stop(); } }
public void SendToAllButCaller() { var host = new MemoryHost(); host.MapConnection<FilteredConnection>("/filter"); var connection1 = new Client.Connection("http://foo/filter"); var connection2 = new Client.Connection("http://foo/filter"); var wh1 = new ManualResetEventSlim(initialState: false); var wh2 = new ManualResetEventSlim(initialState: false); connection1.Received += data => wh1.Set(); connection2.Received += data => wh2.Set(); connection1.Start(host).Wait(); connection2.Start(host).Wait(); connection1.Send("test").Wait(); Assert.False(wh1.WaitHandle.WaitOne(TimeSpan.FromSeconds(5))); Assert.True(wh2.WaitHandle.WaitOne(TimeSpan.FromSeconds(5))); connection1.Stop(); connection2.Stop(); }
public void FarmDisconnectOnlyRaisesEventOnce() { // Each node shares the same bus but are indepenent servers var counters = new SignalR.Infrastructure.PerformanceCounterManager(); var configurationManager = new DefaultConfigurationManager(); using (var bus = new MessageBus(new StringMinifier(), new TraceManager(), counters, configurationManager)) { var nodeCount = 3; var nodes = new List<ServerNode>(); for (int i = 0; i < nodeCount; i++) { nodes.Add(new ServerNode(bus)); } var timeout = TimeSpan.FromSeconds(5); foreach (var node in nodes) { node.Server.Configuration.HeartbeatInterval = timeout; node.Server.Configuration.DisconnectTimeout = TimeSpan.Zero; node.Server.MapConnection<FarmConnection>("/echo"); } var loadBalancer = new LoadBalancer(nodes.Select(f => f.Server).ToArray()); var transport = new Client.Transports.LongPollingTransport(loadBalancer); var connection = new Client.Connection("http://goo/echo"); connection.Start(transport).Wait(); for (int i = 0; i < nodes.Count; i++) { nodes[i].Broadcast(String.Format("From Node {0}: {1}", i, i + 1)); Thread.Sleep(TimeSpan.FromSeconds(1)); } connection.Stop(); Thread.Sleep(TimeSpan.FromTicks(timeout.Ticks * nodes.Count)); Assert.Equal(1, nodes.Sum(n => n.Connection.DisconnectCount)); } }
public void SendCanBeCalledAfterStateChangedEvent(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(); var connection = new Client.Connection(host.Url + "/multisend"); var results = new List<string>(); connection.Received += data => { results.Add(data); }; connection.StateChanged += stateChange => { if (stateChange.NewState == Client.ConnectionState.Connected) { connection.SendWithTimeout(""); } }; connection.Start(host.Transport).Wait(); Thread.Sleep(TimeSpan.FromSeconds(5)); connection.Stop(); Debug.WriteLine(String.Join(", ", results)); Assert.Equal(4, results.Count); } }
public void FallbackToLongPollingWorks(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(); var connection = new Client.Connection(host.Url + "/fall-back"); connection.Start(host.Transport).Wait(); Assert.Equal(connection.Transport.Name, "longPolling"); connection.Stop(); } }
public void SendToGroupFromOutsideOfConnection() { using (var host = new MemoryHost()) { IPersistentConnectionContext connectionContext = null; host.Configure(app => { var configuration = new ConnectionConfiguration { Resolver = new DefaultDependencyResolver() }; app.MapConnection<FilteredConnection>("/echo", configuration); connectionContext = configuration.Resolver.Resolve<IConnectionManager>().GetConnectionContext<FilteredConnection>(); }); var connection1 = new Client.Connection("http://foo/echo"); var wh1 = new ManualResetEventSlim(initialState: false); connection1.Start(host).Wait(); connection1.Received += data => { Assert.Equal("yay", data); wh1.Set(); }; connectionContext.Groups.Add(connection1.ConnectionId, "Foo").Wait(); connectionContext.Groups.Send("Foo", "yay"); Assert.True(wh1.Wait(TimeSpan.FromSeconds(10))); connection1.Stop(); } }
public static IDisposable ClientGroupsSyncWithServerGroupsOnReconnectLongPolling() { var host = new MemoryHost(); host.Configure(app => { var config = new ConnectionConfiguration() { Resolver = new DefaultDependencyResolver() }; app.MapConnection<MyRejoinGroupConnection>("/groups", config); var configuration = config.Resolver.Resolve<IConfigurationManager>(); configuration.KeepAlive = null; configuration.ConnectionTimeout = TimeSpan.FromSeconds(1); }); var connection = new Client.Connection("http://foo/groups"); var inGroupOnReconnect = new List<bool>(); var wh = new ManualResetEventSlim(); connection.Received += message => { Console.WriteLine(message); wh.Set(); }; connection.Reconnected += () => { connection.Send(new { type = 3, group = "test", message = "Reconnected" }).Wait(); }; connection.Start(new Client.Transports.LongPollingTransport(host)).Wait(); // Join the group connection.Send(new { type = 1, group = "test" }).Wait(); Thread.Sleep(TimeSpan.FromSeconds(10)); if (!wh.Wait(TimeSpan.FromSeconds(10))) { Debugger.Break(); } Console.WriteLine(inGroupOnReconnect.Count > 0); Console.WriteLine(String.Join(", ", inGroupOnReconnect.Select(b => b.ToString()))); connection.Stop(); return host; }
public void GroupsRejoinedWhenOnRejoiningGroupsOverridden() { var host = new MemoryHost(); host.Configuration.KeepAlive = null; host.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(2); host.Configuration.HeartBeatInterval = TimeSpan.FromSeconds(2); host.MapConnection<MyRejoinGroupConnection>("/groups"); var connection = new Client.Connection("http://foo/groups"); var list = new List<string>(); connection.Received += data => { list.Add(data); }; connection.Start(host).Wait(); // Join the group connection.Send(new { type = 1, group = "test" }).Wait(); // Sent a message connection.Send(new { type = 3, group = "test", message = "hello to group test" }).Wait(); // Force Reconnect Thread.Sleep(TimeSpan.FromSeconds(5)); // Send a message connection.Send(new { type = 3, group = "test", message = "goodbye to group test" }).Wait(); Thread.Sleep(TimeSpan.FromSeconds(5)); connection.Stop(); Assert.Equal(2, list.Count); Assert.Equal("hello to group test", list[0]); Assert.Equal("goodbye to group test", list[1]); }
public void GroupsReceiveMessages() { var host = new MemoryHost(); host.MapConnection<MyGroupConnection>("/groups"); var connection = new Client.Connection("http://foo/groups"); var list = new List<string>(); connection.Received += data => { list.Add(data); }; connection.Start(host).Wait(); // Join the group connection.Send(new { type = 1, group = "test" }).Wait(); // Sent a message connection.Send(new { type = 3, group = "test", message = "hello to group test" }).Wait(); // Leave the group connection.Send(new { type = 2, group = "test" }).Wait(); // Send a message connection.Send(new { type = 3, group = "test", message = "goodbye to group test" }).Wait(); Thread.Sleep(TimeSpan.FromSeconds(5)); connection.Stop(); Assert.Equal(1, list.Count); Assert.Equal("hello to group test", list[0]); }
public void ConnectionIdsCantBeUsedAsGroups() { using (var host = new MemoryHost()) { IProtectedData protectedData = null; host.Configure(app => { var config = new ConnectionConfiguration { Resolver = new DefaultDependencyResolver() }; app.MapConnection <MyConnection>("/echo", config); protectedData = config.Resolver.Resolve <IProtectedData>(); }); var connection = new Client.Connection("http://memoryhost/echo"); var connectionTcs = new TaskCompletionSource <string>(); var spyTcs = new TaskCompletionSource <string>(); connection.Received += data => { connectionTcs.SetResult(data.Trim()); }; connection.Start(host).Wait(); var tcs = new TaskCompletionSource <object>(); EventSourceStreamReader reader = null; Task.Run(async() => { try { string url = GetUrl(protectedData, connection); var response = await host.Get(url); reader = new EventSourceStreamReader(connection, response.GetStream()); reader.Message = sseEvent => { if (sseEvent.EventType == EventType.Data && sseEvent.Data != "initialized" && sseEvent.Data != "{}") { spyTcs.TrySetResult(sseEvent.Data); } }; reader.Start(); tcs.TrySetResult(null); } catch (Exception ex) { tcs.TrySetException(ex); } }); tcs.Task.Wait(); connection.SendWithTimeout("STUFFF"); Assert.True(connectionTcs.Task.Wait(TimeSpan.FromSeconds(5))); Assert.Equal("STUFFF", connectionTcs.Task.Result); Assert.False(spyTcs.Task.Wait(TimeSpan.FromSeconds(5))); connection.Stop(); } }
public void ReconnectFiresAfterHostShutDown(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(); var connection = new Client.Connection(host.Url + "/my-reconnect"); connection.Start(host.Transport).Wait(); host.Shutdown(); Thread.Sleep(TimeSpan.FromSeconds(5)); Assert.Equal(Client.ConnectionState.Reconnecting, connection.State); connection.Stop(); } }
public void SendRaisesOnReceivedFromAllEvents(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(); var connection = new Client.Connection(host.Url + "/multisend"); var results = new List<string>(); connection.Received += data => { results.Add(data); }; connection.Start(host.Transport).Wait(); connection.SendWithTimeout(""); Thread.Sleep(TimeSpan.FromSeconds(5)); connection.Stop(); Debug.WriteLine(String.Join(", ", results)); Assert.Equal(4, results.Count); Assert.Equal("OnConnectedAsync1", results[0]); Assert.Equal("OnConnectedAsync2", results[1]); Assert.Equal("OnReceivedAsync1", results[2]); Assert.Equal("OnReceivedAsync2", results[3]); } }
public async Task DisconnectFiresForPersistentConnectionWhenClientCallsStop() { using (var host = new MemoryHost()) { var connectWh = new AsyncManualResetEvent(); var disconnectWh = new AsyncManualResetEvent(); var dr = new DefaultDependencyResolver(); var configuration = dr.Resolve<IConfigurationManager>(); host.Configure(app => { var config = new ConnectionConfiguration { Resolver = dr }; app.MapSignalR<MyConnection>("/echo", config); configuration.DisconnectTimeout = TimeSpan.FromSeconds(6); dr.Register(typeof(MyConnection), () => new MyConnection(connectWh, disconnectWh)); }); var connection = new Client.Connection("http://foo/echo"); // Maximum wait time for disconnect to fire (3 heart beat intervals) var disconnectWait = TimeSpan.FromTicks(configuration.HeartbeatInterval().Ticks * 3); await connection.Start(host); Assert.True(await connectWh.WaitAsync(TimeSpan.FromSeconds(10)), "Connect never fired"); connection.Stop(); Assert.True(await disconnectWh.WaitAsync(disconnectWait), "Disconnect never fired"); } }
public void EnvironmentIsAvailable(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(); var connection = new Client.Connection(host.Url + "/items"); var connection2 = new Client.Connection(host.Url + "/items"); var results = new List<RequestItemsResponse>(); connection2.Received += data => { var val = JsonConvert.DeserializeObject<RequestItemsResponse>(data); if (!results.Contains(val)) { results.Add(val); } }; connection.Start(host.Transport).Wait(); connection2.Start(host.Transport).Wait(); Thread.Sleep(TimeSpan.FromSeconds(2)); connection.SendWithTimeout(null); Thread.Sleep(TimeSpan.FromSeconds(2)); connection.Stop(); Thread.Sleep(TimeSpan.FromSeconds(2)); Debug.WriteLine(String.Join(", ", results)); Assert.Equal(3, results.Count); Assert.Equal("OnConnectedAsync", results[0].Method); Assert.Equal(1, results[0].Keys.Length); Assert.Equal("owin.environment", results[0].Keys[0]); Assert.Equal("OnReceivedAsync", results[1].Method); Assert.Equal(1, results[1].Keys.Length); Assert.Equal("owin.environment", results[1].Keys[0]); Assert.Equal("OnDisconnectAsync", results[2].Method); Assert.Equal(1, results[2].Keys.Length); Assert.Equal("owin.environment", results[2].Keys[0]); connection2.Stop(); } }
public void SendWithSyncErrorThrows(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(); var connection = new Client.Connection(host.Url + "/sync-error"); connection.Start(host.Transport).Wait(); Assert.Throws<AggregateException>(() => connection.SendWithTimeout("test")); connection.Stop(); } }
public void ReconnectFiresAfterHostShutDown() { var host = new MemoryHost(); var conn = new MyReconnect(); host.DependencyResolver.Register(typeof(MyReconnect), () => conn); host.MapConnection<MyReconnect>("/endpoint"); var connection = new Client.Connection("http://foo/endpoint"); connection.Start(host).Wait(); host.Dispose(); Thread.Sleep(TimeSpan.FromSeconds(5)); Assert.Equal(Client.ConnectionState.Reconnecting, connection.State); connection.Stop(); }
public void ReconnectDoesntFireAfterTimeOut(TransportType transportType, MessageBusType messageBusType) { using (var host = new MemoryHost()) { var conn = new MyReconnect(); host.Configure(app => { var config = new ConnectionConfiguration { Resolver = new DefaultDependencyResolver() }; UseMessageBus(messageBusType, config.Resolver); app.MapSignalR<MyReconnect>("/endpoint", config); var configuration = config.Resolver.Resolve<IConfigurationManager>(); configuration.DisconnectTimeout = TimeSpan.FromSeconds(6); configuration.ConnectionTimeout = TimeSpan.FromSeconds(2); configuration.KeepAlive = null; config.Resolver.Register(typeof(MyReconnect), () => conn); }); var connection = new Client.Connection("http://foo/endpoint"); var transport = CreateTransport(transportType, host); connection.Start(transport).Wait(); Thread.Sleep(TimeSpan.FromSeconds(5)); connection.Stop(); Assert.Equal(0, conn.Reconnects); } }
public void ReconnectFiresAfterTimeOutSSE() { var host = new MemoryHost(); var conn = new MyReconnect(); host.Configuration.KeepAlive = null; host.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(5); host.Configuration.HeartBeatInterval = TimeSpan.FromSeconds(5); host.DependencyResolver.Register(typeof(MyReconnect), () => conn); host.MapConnection<MyReconnect>("/endpoint"); var connection = new Client.Connection("http://foo/endpoint"); connection.Start(new Client.Transports.ServerSentEventsTransport(host)).Wait(); Thread.Sleep(TimeSpan.FromSeconds(15)); connection.Stop(); Assert.InRange(conn.Reconnects, 1, 4); }
public void GroupCanBeAddedAndMessagedOnConnected(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { var wh = new ManualResetEventSlim(); host.Initialize(); var connection = new Client.Connection(host.Url + "/add-group"); connection.Received += data => { Assert.Equal("hey", data); wh.Set(); }; connection.Start(host.Transport).Wait(); connection.SendWithTimeout(""); Assert.True(wh.Wait(TimeSpan.FromSeconds(5))); connection.Stop(); } }
public void GroupsRejoinedWhenOnRejoiningGroupsOverridden(HostType hostType, TransportType transportType) { using (var host = CreateHost(hostType, transportType)) { host.Initialize(keepAlive: 0, connectionTimeout: 2, hearbeatInterval: 2); var connection = new Client.Connection(host.Url + "/rejoin-groups"); var list = new List<string>(); connection.Received += data => { list.Add(data); }; connection.Start(host.Transport).Wait(); // Join the group connection.SendWithTimeout(new { type = 1, group = "test" }); // Sent a message connection.SendWithTimeout(new { type = 3, group = "test", message = "hello to group test" }); // Force Reconnect Thread.Sleep(TimeSpan.FromSeconds(5)); // Send a message connection.SendWithTimeout(new { type = 3, group = "test", message = "goodbye to group test" }); Thread.Sleep(TimeSpan.FromSeconds(5)); connection.Stop(); Assert.Equal(2, list.Count); Assert.Equal("hello to group test", list[0]); Assert.Equal("goodbye to group test", list[1]); } }
public void GroupsAreNotReadOnConnectedAsyncLongPolling() { var host = new MemoryHost(); host.MapConnection<MyConnection>("/echo"); var connection = new Client.Connection("http://foo/echo"); connection.Groups = new List<string> { typeof(MyConnection).FullName + ".test" }; connection.Received += data => { Assert.False(true, "Unexpectedly received data"); }; var transport = new Client.Transports.LongPollingTransport(host); connection.Start(transport).Wait(); Thread.Sleep(TimeSpan.FromSeconds(5)); connection.Stop(); }
public void SendRaisesOnReceivedFromAllEvents() { var host = new MemoryHost(); host.MapConnection<MySendingConnection>("/multisend"); var connection = new Client.Connection("http://foo/multisend"); var results = new List<string>(); connection.Received += data => { results.Add(data); }; connection.Start(host).Wait(); connection.Send("").Wait(); Thread.Sleep(TimeSpan.FromSeconds(5)); connection.Stop(); Debug.WriteLine(String.Join(", ", results)); Assert.Equal(4, results.Count); Assert.Equal("OnConnectedAsync1", results[0]); Assert.Equal("OnConnectedAsync2", results[1]); Assert.Equal("OnReceivedAsync1", results[2]); Assert.Equal("OnReceivedAsync2", results[3]); }
public void ReconnectFiresAfterTimeOut(TransportType transportType) { using (var host = new MemoryHost()) { var conn = new MyReconnect(); host.Configure(app => { var config = new ConnectionConfiguration { Resolver = new DefaultDependencyResolver() }; app.MapConnection<MyReconnect>("/endpoint", config); var configuration = config.Resolver.Resolve<IConfigurationManager>(); configuration.KeepAlive = 0; configuration.ConnectionTimeout = TimeSpan.FromSeconds(5); configuration.HeartbeatInterval = TimeSpan.FromSeconds(5); config.Resolver.Register(typeof(MyReconnect), () => conn); }); var connection = new Client.Connection("http://foo/endpoint"); var transport = CreateTransport(transportType, host); connection.Start(transport).Wait(); Thread.Sleep(TimeSpan.FromSeconds(15)); connection.Stop(); Assert.InRange(conn.Reconnects, 1, 4); } }