Implementing SSE into your application can go in two ways, in a simple way or the hard way but I prefer the simple dumb way..
SSE
In nutshell SSE is a long running http connection, with a special mime type (text/event-stream) and can be used for connection between a server and a client, where messages can be sent from server to client in continuous manner. If you plan to send a data in real time without the need to submitting requests from the client to the server asking for the data you can use SSE, perfect examples are real-time stock market data to be sent from an exchange's backend to it frontend.
Setting up the project
To keep the the solution as simple as possible, we gonna implement the solution in three files.
Object to hold the connection
We need a class to hold Http instance to write a data to it, and you can put the rest of the properties as you need.
public class SSEClient
{
public string Id { get; set; }
// to keep http instance for data transfer
public HttpResponse Response { get; set; }
public DateTime JoinTime { get; set; }
private SSEClient(HttpResponse response) =>
(Id, Response, JoinTime) = (Guid.NewGuid().ToString(), response, DateTime.Now);
public static SSEClient New(HttpResponse response) =>
new SSEClient(response);
}
Setting helper class
Helper class does three things, setting up necessary http headers for SSE, keeps the http connection active and send data to a client
public static async Task SetSSEHeaders(this HttpResponse response)
{
# don't cache the response
response.Headers.Add("Cache-Control", "no-cache");
# content type to let the browser knows it's SSE
response.Headers.Add("Content-Type", "text/event-stream");
#
response.Headers.Add("X-Accel-Buffering", "no");
# keep the connection a live for data flow
response.Headers.Add("Connection", "keep-alive");
await response.Body.FlushAsync();
}
public static async Task KeepSSEAlive(this HttpResponse _, CancellationToken cancellationToken) {
while (!cancellationToken.IsCancellationRequested)
await Task.Delay(3000, cancellationToken);
}
public static async Task SendSEEEvent(this HttpResponse response, string @event)
{
await response.WriteAsync($"data: {@event}\r\r");
await response.Body.FlushAsync();
}
Holding Active Connections
A simple class to hold the connections for brodcasting data and registering connections
public class SSEProvider
{
public readonly ConcurrentBag<SSEClient> Clients;
public SSEProvider() =>
Clients = new ConcurrentBag<SSEClient>();
private ParallelOptions ParallelOptions =>
new () { MaxDegreeOfParallelism = 5 };
public void Brodcast(string @event) =>
Parallel.ForEach(Clients, ParallelOptions, client =>
{
client.Response.SendSEEEvent(@event).Wait();
});
public void Register(HttpContext context) =>
Clients.Add(SSEClient.New(context.Response));
}
How to use the code
Now register the SSEProvider
as singleton service
builder.Services.AddSingleton(typeof(SSEProvider), typeof(SSEProvider));
And create a controller or a controller you already have and put the following code in it
.....
public class LiveController : Controller
{
private readonly SSEProvider SSEProvider;
public LiveController(SSEProvider sseProvider) =>
SSEProvider = sseProvider;
[HttpGet("[action]")]
public async Task Listen(CancellationToken cancellationToken)
{
await HttpContext.Response.SetSSEHeaders();
SSEProvider.Register(HttpContext);
await HttpContext.Response.KeepSSEAlive(cancellationToken);
}
}
Now when you call SSEProvider.Brodcast("the data here");
you can send the data to all connected clients
At last
The code provided was only tested on low load applications, you might face issues on higher load applications but it's good for simple use cases, if you have any questions feel free to drop a comment.
If you like the my coding style for learning consider checking out my open source projects
Top comments (0)