How to consume an SSE endpoint using HttpClient in C#

By FoxLearn 1/22/2025 2:58:14 AM   5
Server-Sent Events (SSE) offer an efficient way for clients to receive real-time updates from a server.

Unlike traditional polling where the client regularly requests data from the server, SSE allows the server to push updates to the client through a one-way stream. This can significantly reduce overhead and provide more efficient data delivery for use cases like live notifications, stock price updates, or real-time feeds.

If you are working with C# and need to consume an SSE endpoint, you can do so using the HttpClient class.

For example, how to consume an SSE stream using HttpClient in C#:

using (var streamReader = new StreamReader(await httpClient.GetStreamAsync(url)))
{
    while (!streamReader.EndOfStream)
    {
        var message = await streamReader.ReadLineAsync();
        Console.WriteLine($"Received message: {message}");
    }
}

This code opens a stream to the specified URL, continuously reads new messages from the SSE stream, and outputs them to the console.

SSE Client for Monitoring Weather Updates

Let’s now consider an example where you are consuming weather updates from a weather service that broadcasts data using SSE. The client will continuously receive real-time updates on temperature, humidity, and weather conditions.

Here’s the C# console app that subscribes to a weather update stream:

static async Task Main(string[] args)
{
    HttpClient client = new HttpClient();
    client.Timeout = TimeSpan.FromSeconds(10);
    string city = "SanFrancisco";
    string url = $"http://localhost:9000/weatherupdates/{city}";

    while (true)
    {
        try
        {
            Console.WriteLine("Connecting to the weather service...");
            using (var streamReader = new StreamReader(await client.GetStreamAsync(url)))
            {
                while (!streamReader.EndOfStream)
                {
                    var message = await streamReader.ReadLineAsync();
                    Console.WriteLine($"Received weather update: {message}");
                }
            }
        }
        catch (Exception ex)
        {
            // Handle connection errors or timeouts
            Console.WriteLine($"Error: {ex.Message}");
            Console.WriteLine("Retrying in 10 seconds...");
            await Task.Delay(TimeSpan.FromSeconds(10));
        }
    }
}

In this example:

  • HttpClient Setup: The HttpClient is configured with a timeout of 10 seconds to ensure that if the connection takes too long, it will fail gracefully.
  • SSE Connection: The client connects to an SSE endpoint that provides weather updates for a specific city (e.g., "SanFrancisco").
  • Stream Processing: The app listens for weather updates. Each time a new update arrives, the app processes and displays it in the console.
  • Error Handling & Retrying: If an error occurs, such as a network issue or a timeout, the app retries the connection after waiting for 10 seconds.

 Here’s how the output of the console application might look during execution:

Connecting to the weather service...
Received weather update: Temperature=65°F Humidity=55% Condition=Clear
Received weather update: Temperature=66°F Humidity=54% Condition=Clear
Received weather update: Temperature=67°F Humidity=53% Condition=Clear
Error: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host...
Retrying in 10 seconds...
Connecting to the weather service...
Received weather update: Temperature=68°F Humidity=52% Condition=Clear
Received weather update: Temperature=69°F Humidity=51% Condition=Clear

This output demonstrates the app receiving real-time weather updates and handling temporary connection issues by retrying the connection.

Improving Retry Logic with Polly

While the basic retry logic works, for production applications, it’s recommended to use a more sophisticated library like Polly.

Polly is a .NET resilience and transient-fault-handling library that allows you to implement more robust retry policies, such as exponential backoff, timeouts, and circuit breakers.

For example, how you can integrate Polly into your SSE client:

using Polly;
using Polly.Retry;

static async Task Main(string[] args)
{
    var policy = Policy
        .Handle<Exception>()
        .WaitAndRetryAsync(5, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));

    HttpClient client = new HttpClient();
    client.Timeout = TimeSpan.FromSeconds(10);
    string city = "SanFrancisco";
    string url = $"http://localhost:9000/weatherupdates/{city}";

    while (true)
    {
        await policy.ExecuteAsync(async () =>
        {
            Console.WriteLine("Connecting to the weather service...");
            using (var streamReader = new StreamReader(await client.GetStreamAsync(url)))
            {
                while (!streamReader.EndOfStream)
                {
                    var message = await streamReader.ReadLineAsync();
                    Console.WriteLine($"Received weather update: {message}");
                }
            }
        });
    }
}

This example uses Polly to implement an exponential backoff strategy for retries. It will wait for 1, 2, 4, 8, and 16 seconds before retrying the connection up to 5 times in case of failure.

Consuming an SSE endpoint in C# is simple with HttpClient. Whether you’re developing real-time applications like weather monitoring, stock price updates, or live notifications, this approach ensures you can efficiently receive data streams from the server.