Skip to content

[API Proposal]: Add formatting support to System.Net.ServerSentEvents #109294

@eiriktsarpalis

Description

@eiriktsarpalis

Background and motivation

The System.Net.ServerSentEvents library added support for parsing SSE events. We should also add support for formatting SSE events on the server side, in a way that unlocks work for dotnet/aspnetcore#56172.

API Proposal

namespace System.Net.ServerSentEvents;

// Extends the existing type to support writing
public readonly struct SseItem<T>
{
-   public SseItem(T data, string eventType);
+   public SseItem(T data, string? eventType);

    public T Data { get; }

-   public string EventType { get; }
+   public string? EventType { get; } 

+   public string? EventId { get; init; }
}

public delegate T SseItemParser<out T>(string eventType, ReadOnlySpan<byte> data);
+public delegate byte[] SseItemFormatter<T>(T value);

+public sealed class SseFormatter
+{
+    public static SseFormatter Create<T>(IAsyncEnumerable<SseItem<string>> source);
+    public static SseFormatter Create<T>(IAsyncEnumerable<SseItem<T>> source, SseItemFormatter<T> itemFormatter);
+
+    public Task WriteToStreamAsync(Stream stream, CancellationToken cancellationToken = default);
+    public void WriteToStream(Stream stream, CancellationToken cancellationToken = default);
+}

API Usage

IAsyncEnumerable<SseItem<int>> source = ...;
SseFormatter formatter = SseFormatter.Create<int>(source, value => JsonSerializer.SerializeToBytes(value));
await formatter.WriteToStreamAsync(targetStream, cancellationToken);

Alternative Designs

The current SseItemFormatter delegate uses an allocating design, but which is simpler to use. Alternative designs would involve writing either to a Stream:

public delegate ValueTask SseItemFormatter<T>(Stream stream, T value);

But this would require wrapping the underlying stream in order to intercept potential line breaks. Alternatively, we could use an IBufferWriter<byte> which also composes better with PipeWriter targets:

public delegate void SseItemFormatter<T>(IBufferWriter<byte> writer, T value);

The downside of either approach is that it becomes harder to define formatters from the user's perspective:

SseFormatter formatter = SseFormatter.Create<int>(source, (bufferWriter, value) => 
{ 
    var writer = new Utf8JsonWriter(bufferWriter);
    return JsonSerializer.SerializeToBytes(bufferWriter, value); 
});

Risks

No response

Metadata

Metadata

Labels

api-approvedAPI was approved in API review, it can be implementedarea-System.Netin-prThere is an active PR which will close this issue when it is merged

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions