💾 Archived View for capsule.adrianhesketh.com › 2015 › 04 › 19 › realtime-sparklines-with-signalr captured on 2023-06-16 at 16:37:24. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2021-11-30)
-=-=-=-=-=-=-
On a system I've been working on, users have their file processing tasks placed in a queue, and I wanted to find a way to show the users if there was a queue, and if so, how quickly the queue was being processed.
The Windows Services which execute the tasks already publish RabbitMQ messages containing the performance data I needed, but I needed a way to get this data visible on the Web UI.
To try out some ideas, I put together a proof-of-concept using a Windows Service which subscribes to the performance data RabbitMQ messages, then publishes aggregate statistics over to a SignalR hub via the .Net SignalR client.
Client-side Sparklines are then generated using [0] and kept up-to-date with new information via SignalR.
This worked pretty well, and didn't take much effort to implement. For simpler architectures, just pushing directly to the SignalR endpoint from the Windows Service (if the SignalR endpoint is up and available) would be reasonable, albeit tightly coupled and not fault tolerant. I've included this approach as an example.
To make sure that the users see something straight away instead of having to wait for the sparkline to fill, I added a cache to the SignalR hub so that something is always on screen:
{{< gist a-h 3660a6729c3450b38731 >}}
You end up with a result like this:
As well as receiving data from the endpoint, any connected client can send data over SignalR, e.g. from the JavaScript console, you can write code like this:
namespace SparkLines.Hubs { public class SparklinesHub : Hub { static ConcurrentDictionary<string, List<int>> Values = new ConcurrentDictionary<string, List<int>>(); static int maximumValues = 49; public void PublishMetric(string name, int value) { if (IsAuthorised()) { // Update the dictionary with new data. Values.AddOrUpdate(name, new List<int>() { value }, (k, v) => { int skip = v.Count > maximumValues ? v.Count - maximumValues : 0; return v.Skip(skip).Concat(new int[] { value }).ToList(); }); var valuesToSend = new List<int>() { }; Values.TryGetValue(name, out valuesToSend); // Publish the metrics data to connected clients. Clients.All.metricsUpdated(name, valuesToSend); } } private bool IsAuthorised() { return base.Context.Headers["PublisherKey"] == "xy123123"; } } }
This could be used by an attacker to flood the users of the site with on-screen updates or to carry out DDoS attacks by using the system's realtime capability against itself, or could be abused in many other ways.
In my example case, only the .Net client at the server side should be able to send metric samples out to clients. A cheap way of limiting access is to add a header to the request which contains a publishing key (i.e. a password), and check for its presence at the server, which I've done in my proof-of-concept. It could be done better, e.g. by use of a custom attribute, but it works well enough to try out the idea.
You can download the Visual Studio Solution at [1] if you want to try it out yourself.
Migrating from Ninject to Simple Injector