Introduction
As a developer, testing network conditions and connection persistence can be a challenge. Sometimes, network transportation isn’t always reliable, and it’s not easy to simulate a poor network connection during testing. Fortunately, I recently discovered clumsy, a tool that made it easy for me to test my SignalR connection persistence in a C# project. In this article, I will explain how clumsy was helpful for me and how it can be helpful for other developers.
Hub client
First let me show you the hub client I’m using in my project:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
using System.Net; using System.Security; using Polly; using Serilog; using Microsoft.AspNet.SignalR.Client; namespace Test.SignalR; /// <summary> /// SignalR Hub client implementation for ASP.NET /// </summary> public class TestHubClient : IAsyncDisposable { private HubConnection _connection; private IHubProxy _hubProxy; public event Func<Task>? Disconnected; public string HubUrl { get; } public ILogger Logger { get; } public ConnectionState State => _connection.State; private bool _tryingToReconnect; public TestHubClient(string hubUrl, SecureString key, ILogger? logger = null) { HubUrl = hubUrl; Logger = logger ?? Log.Logger; _connection = new HubConnection(HubUrl, false); _hubProxy = _connection.CreateHubProxy("testHub"); // Setup events _connection.Reconnecting += OnReconnecting; _connection.Reconnected += OnReconnected; _connection.Closed += OnClosed; _connection.Error += OnError; _connection.ConnectionSlow += OnConnectionSlow; } /// <summary> /// When invoked, client will automatically try to reconnect when connection is closed /// </summary> public void WithAutomaticReconnect() { Disconnected += async () => { if (!_tryingToReconnect) { Logger.Debug("Reconnecting after 5 seconds"); await Task.Delay(TimeSpan.FromSeconds(5)); await ConnectAsync(CancellationToken.None); } }; } private void OnClosed() { Logger.Warning("Disconnected from hub: {HubUrl}", HubUrl); Disconnected?.Invoke(); } private void OnReconnected() { Logger.Information("Reconnected to hub: {HubUrl}", HubUrl); } private void OnReconnecting() { Logger.Debug("Reconnecting to hub: {HubUrl}", HubUrl); } private void OnError(Exception exception) { Logger.Warning(exception, "Error occured in hub: {HubUrl}", HubUrl); } private void OnConnectionSlow() { Logger.Warning("Slow connection to hub: {HubUrl}", HubUrl); } public async Task ConnectAsync(CancellationToken ct) { try { _tryingToReconnect = true; // Here we setup Polly policy to retry connection forever // with wait time between each attempt increasing to maximum of 1 minute await Policy .Handle<Exception>() .WaitAndRetryForeverAsync( retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)).Clamp(TimeSpan.FromMinutes(1)), (exception, timespan) => { Logger.Warning(exception, "Error occured while trying to connect to hub {HubUrl}. Another attempt after {TimeSpan}", HubUrl, timespan); }) .ExecuteAsync(async () => { ct.ThrowIfCancellationRequested(); Logger.Debug("Connecting to {HubUrl}", HubUrl); await _connection.Start(); Logger.Information("Connected to hub: {HubUrl}", HubUrl); }); } finally { _tryingToReconnect = false; } } public Task DisconnectAsync() { _connection.Stop(); return Task.CompletedTask; } public ValueTask DisposeAsync() { _connection.Dispose(); return ValueTask.CompletedTask; } } |
Solution
I used clumsy to intercept and manipulate network packets.
Here is one scenario that I used. I started my C# application that is using SignalR(old ASP.Net 🙄), letting it successfully connect to the hub. I then started clumsy, setting the filter to ip.DstAddr == 123.45.67.89
where the ip address 123.45.67.89 being the address of my SignalR hub. I then set Lag – delay to 1000ms and Drop chance to 90%. After hitting the Start button in clumsy inteface and little bit of a delay, I could see messages in console showing how my app was disconnected and is trying to reconnect. Note that I’m using Polly and it’s retry policies.
1 2 3 |
[13:07:34 WRN] Disconnected from hub: https://example.com/signalr/ [13:07:38 DBG] Connecting to https://example.com/signalr/ [13:13:09 WRN] Error occured while trying to connect to hub https://example.com/signalr/. Another attempt after 00:00:32 |
After hitting Stop button in clumsy, my application was able to successfully establish the SignalR connection again.
1 2 |
[13:13:41 DBG] Connecting to https://example.com/signalr/ [13:13:41 INF] Connected to hub: https://example.com/signalr/ |
Using clumsy, I was able to test and debug my SignalR connection persistence quickly and efficiently. The tool allowed me to simulate various network conditions and observe how my application would handle them. I did not need to change any code in my application or install any additional software, making it a great tool for quick testing.
Conclusion
In conclusion, clumsy is an excellent tool for developers who want to test their application’s behavior under poor network conditions. It’s user-friendly GUI(It also has CLI! 👍), simple setup process, and the ability to intercept and manipulate network packets make it an essential tool in any developer’s toolbox. I was able to use clumsy to test my SignalR connection persistence quickly and efficiently, without needing to modify my application’s code. I highly recommend it to any developer who wants to ensure that their application can handle a wide range of network conditions.