Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implement sending Server Sent Events in C# (no ASP.NET / MVC / ...)

For a project, I need to implement SSE (Server Sent Events) in a C# Application. Although this may sound easy, I've got no clue how to solve this.

As I'm new to C# (though, not new to programming in general) I took an excursion to Google and tried to look for some sample code. From what I've seen so far, I could learn to build a HTTP Server with C# or consume server sent events. But I found nothing about sending SSEs.

What I'm trying to get my head around: how can I keep sending updated data over the incoming request? Normally, you get a request, do your thing and reply. Done, connection closed. But in this case I want to kind of "stick" to the response-stream and send new data through, each time an event in my application fires.

The problem, for me, lies in this event-based approach: it's not some intervall-based polling and updating. It's rather that the app goes like "Hey, something happend. I really should tell you about it!"

TL;DR: how can I hold on to that response-stream and send updates - not based on loops or timers, but each time certain events fire?

Also, before I forget: I know, there are libraries out there doing just that. But from what I've seen so far (and from what I've understood; correct me if I'm wrong) those solutions depend on ASP.NET / MVC / you name it. And as I'm just writing a "plain" C# application, I don't think I meet these requirements.

like image 208
mh166 Avatar asked Jun 30 '17 17:06

mh166


People also ask

How are server-sent events implemented?

When working with Server Sent Events, communications between client and server are initiated by the client (browser). The client creates a new JavaScript EventSource object, passing it the URL of an endpoint which is expected to return a stream of events over time.

What is server-sent events in spring boot?

Simply put, Server-Sent-Events, or SSE for short, is an HTTP standard that allows a web application to handle a unidirectional event stream and receive updates whenever server emits data. Spring 4.2 version already supported it, but starting with Spring 5, we now have a more idiomatic and convenient way to handle it.

What is SSE connection?

Server-Sent Events (SSE) is a server push technology enabling a client to receive automatic updates from a server via an HTTP connection, and describes how servers can initiate data transmission towards clients once an initial client connection has been established.


1 Answers

As for a light-weight server I would go with an OWIN selfhost WebAPI (https://docs.microsoft.com/en-us/aspnet/web-api/overview/hosting-aspnet-web-api/use-owin-to-self-host-web-api).

A simple server-sent event server action would basically go like:

public class EventController : ApiController
  {
    public HttpResponseMessage GetEvents(CancellationToken clientDisconnectToken)
    {
      var response = Request.CreateResponse();
      response.Content = new PushStreamContent(async (stream, httpContent, transportContext) =>
      {
        using (var writer = new StreamWriter(stream))
        {
          using (var consumer = new BlockingCollection<string>())
          {
            var eventGeneratorTask = EventGeneratorAsync(consumer, clientDisconnectToken);
            foreach (var @event in consumer.GetConsumingEnumerable(clientDisconnectToken))
            {
              await writer.WriteLineAsync("data: " + @event);
              await writer.WriteLineAsync();
              await writer.FlushAsync();
            }
            await eventGeneratorTask;
          }
        }
      }, "text/event-stream");
      return response;
    }

    private async Task EventGeneratorAsync(BlockingCollection<string> producer, CancellationToken cancellationToken)
    {
      try
      {
        while (!cancellationToken.IsCancellationRequested)
        {
          producer.Add(DateTime.Now.ToString(), cancellationToken);
          await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
        }
      }
      finally
      {
        producer.CompleteAdding();
      }
    }
  }

The important part here is the PushStreamContent, which basically just sends the HTTP headers and then leaves the connection open to write the data when it is available.

In my example the events are generated in an extra-task which is given a producer-consumer collection and adds the events (here the current time every second) if they are available to the collection. Whenever a new event arrives GetConsumingEnumerable is automatically notified. The new event is then written in the proper server-sent event format to the stream and flushed. In practice you would need to send some pseudo-ping events every minute or so, as streams which are left open for a long time without data being sent over them would be closed by the OS/framework.

The sample client code to test this would go like:

Write the following code in async method.

using (var client = new HttpClient())
{
  using (var stream = await client.GetStreamAsync("http://localhost:9000/api/event"))
  {
    using (var reader = new StreamReader(stream))
    {
      while (true)
      {
        Console.WriteLine(reader.ReadLine());
      }
    }
  }
}
like image 137
ckuri Avatar answered Sep 18 '22 09:09

ckuri