The starting point
In my first small .NET project, the use of server-sent events for the communication between two microservices (reservation and lending) at the Container Bootcamp was required. Server-sent events have the advantage of not needing a central message component (such as ActiveMq, Kafka, …). Server-sent events are part of the HTML5 specification and use HTTP as transport protocol.
The search for libraries
However, I couldn’t find a fitting library, so it was time to
take matters into my own hands. I discovered a first aspect of my eventual solution in
It describes a self-implemented
IActionResult, and shows how to write strings
to the HTTP response in .NET.
In a previous Scala project, I had already implemented a manual
HTTP connection handling in Akka.
As a fan of Akka, I checked whether an Akka implementation for .NET exists. To my surprise, Akka for .NET was available. Now, I only needed to combine the two.
Akka is based on the concept of actors. My idea was to implement an actor that holds all open connections and another one representing the actual connection.
Initialization of actors
In order to be able to generate the necessary actors, an actor system is
required. This is created in the
Startup.cs, as well the actor,
which will take over the connection handling later on. Since all actors
are of the same type
IActorRef, wrapper classes are used to distinguish
the actors for the dependency injection.
receives the ConnectionHolder via dependency injection.
It also provides an
/events endpoint to which the clients can connect later.
When the endpoint is called, a new instance of
is created and the ConnectionHolder and the return value of the
Content-Type are provided as parameters.
As PushActorStreamResult implements the interface
you get access to the context and the response stream in the method
ExecuteResultAsync. At the same time, the content type for the HTTP response
is set. The
ConnectionHolder receives a message that a new connection
has been opened. This message also transmits the response stream
In the examples I looked at or tried out, the connection was closed at the end of the stream. The client would have to open a new connection over and over again and pick up the latest events. This would imply a certain time lag. It was important to me to keep this time delay as short as possible under normal circumstances.
This is where the Ask comes into play. An Ask in the Akka library
returns a task of the same return type as
Usually, the answer is expected in a certain period of time,
so that a timeout is set when the ask is called.
In this specific case, the timeout is deliberately not set.
Thus, the ask returns a task that normally does not end,
keeping the connection open.
Akka offers the possibility that one actor can generate additional actors, which are ranked under the generating actor in the hierarchy. The ConnectionHolder creates further child actuators, with each child actor representing a single connection.
In comparison to the ConnectionHolder, the ConnectionHandler is much more comprehensive and has different tasks:
- Reading the Akka persistence events
- Writing the server-sent events to the response stream
- Time-controlled sending of the heartbeat to keep the connection open
- Time-controlled checks whether the connection still exists
When you work with events, you need an event store.
This means, that all events can be resent at any time or only a subset of events,
starting at a certain ID. In order for this to work,
Last-Event-ID has to be set
in the request header.
In this project, Akka persistence is used as an event store. A connection to the event store is established via Akka Persistence Query. A PostgreSQL database is used in this project. You can, however, also use other databases, such as MongoDB.
Time-controlled transmission of the heartbeat to keep the connection open
To signal to the client that the connection still exists, a
sent at periodic intervals as a server-sent event comment. A comment is
ignored by the client and only keeps the connection open.
The heartbeat is created by an Akka scheduler.
scheduleTellOnce is used here. After sending a heartbeat, a new scheduler
for the next heartbeat is started. This ensures that a new hearbeat is not
sent until the current one has been successfully sent.
Time-controlled check whether the connection still exists
Checking whether the connection is still open, works according to the same
scheduler principle as for the heartbeat. In the method
the above mentioned
CancelationToken comes into effect. This is called
periodically to check if the connection is still open. If the connection was
closed by the client, the actor is closed via a
PoisonPill and thus the
connection is also closed on the server side.
The client side
Unlike for the server side, there are different libraries for server-sent events. In this project, I used the 3ventic/EvtSource library.
The bigger picture
In the meantime, this type of implementation has also been used productively in various projects and has proven its worth.
Depending on the requirements, the
ConnectionHolder can take on more tasks.
For example, an event could be sent to the
and it distributes that event to the individual
The interested reader will have noticed that the request header
is not taken into account in the example above. This is left to the reader as an exercise.