[API Proposal]: High-level EventSource-style client for Server-Sent Events
- 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
Assessment
This issue has not been assessed yet.