dotnet / dotnet/runtime

[API Proposal]: High-level EventSource-style client for Server-Sent Events

Open
#122,834 3 comments 0 reactions 0 assignees View on GitHub
api-suggestion area-System.Net
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Background and motivation

.NET 10 introduces `System.Net.ServerSentEvents` with `SseParser`, which provides solid low-level support for parsing Server-Sent Events streams. This is a welcome addition and fills an important gap on the protocol side.

However, consuming SSE in real-world applications requires more than parsing. In practice, every client must also handle:
* connection lifecycle (open, closed, faulted)
* transient network failures
* automatic reconnection
* honoring `retry:` values sent by the server
* propagating `Last-Event-ID` on reconnect
* distinguishing cancellation from transport errors

At the moment, all of this logic is left to application code. As a result, each consumer re-implements roughly the same reconnection and lifecycle machinery on top of `HttpClient + SseParser`, with varying quality and subtle differences.

In contrast, the JavaScript `EventSource` API provides a well-understood, widely adopted abstraction that addresses these concerns consistently. The absence of an equivalent high-level client in .NET makes SSE consumption significantly more complex than necessary and discourages adoption for otherwise straightforward streaming scenarios.

The goal of this proposal is **not** to replace `SseParser`, but to build a small, opinionated client abstraction *on top of it*.

### API Proposal

Introduce a **high-level SSE client** in `System.Net.ServerSentEvents` that manages connection lifecycle and reconnection semantics, while delegating parsing to `SseParser`.

The API should:

* Use `HttpClient` internally
* Automatically reconnect on transient failures
* Honor `retry:` values provided by the server
* Automatically send `Last-Event-ID` on reconnect
* Expose explicit lifecycle signals (connected, disconnected, error)

Conceptually, this is a **client-side equivalent** of `EventSource`, adapted to .NET conventions.

Proposal:

```csharp
public sealed class SseClient : IAsyncDisposable
{
public event EventHandler Connected;
public event EventHandler Disconnected;
public event EventHandler Error;
public event EventHandler> Message;

public Task StartAsync(CancellationToken cancellationToken);
public Task StopAsync(CancellationToken cancellationToken);
}
```

This client would internally:

* manage the `HttpClient` request lifecycle
* consume the stream via `SseParser`
* track `LastEventId`
* apply reconnection delays based on `retry:` or defaults

The existing `SseParser` API remains unchanged and continues to serve advanced or custom scenarios.

### API Usage

Example usage from an application perspective:

```csharp
var client = new SseClient(
new Uri("https://example.com/events"),
httpClient);

client.Connected += (_, _) =>
{
logger.LogInformation("SSE connected");
};

client.Message += (_, evt) =>
{
Process(evt.Data);
};

client.Error += (_, ex) =>
{
logger.LogWarning(ex, "SSE error");
};

await client.StartAsync(ct);
```

### Alternative Designs

1. **Status quo (manual composition)**
Continue requiring users to compose `HttpClient + SseParser` themselves.
This works, but leads to duplicated, inconsistent implementations and subtle bugs, especially around reconnection and lifecycle signaling.

2. **Third-party libraries**
Some existing libraries implement EventSource-like behavior, but most predate `SseParser`, use older streaming patterns, or are no longer actively maintained.

3. **Parser-only helpers**
Adding small helpers around `SseParser` (e.g., exposing more metadata) would improve ergonomics but would still leave reconnection and lifecycle management to application code.

### Risks

_No response_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.