-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Add support for ServerSentEventsResult and extension methods #60616
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
{ | ||
ArgumentNullException.ThrowIfNull(httpContext); | ||
|
||
httpContext.Response.ContentType = "text/event-stream"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider following what we do in SignalR
Lines 33-41
https://source.dot.net/#Microsoft.AspNetCore.Http.Connections/Internal/Transports/ServerSentEventsServerTransport.cs,33
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, and line 47 if that's something we care about.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bleh -- I am torn about adding the Firefox workaround here. That seems like something the SseFormatter
should be doing? Although admittedly it's a little gross to hardcode workarounds for buggy clients.
Maybe we leave it out for now and see what kind of feedback we get?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, until we see feedback I think it's less interesting for an API. For SignalR it was important because we want to signal that the connection is active and let the user start sending messages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason not to disable buffering?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That seems like something the
SseFormatter
should be doing?
What is that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should do what SignalR is doing, we should not wait for feedback. Disable buffering and do the crazy IIS workaround 😄 (did you test this in IIS?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That seems like something the SseFormatter should be doing?
SignalR applies a special workaround to send some filler data through the SSE stream before the actual data to force Firefox's EventSource to emit an open event. It's a reaction to this bug in Firefox. The discussion is around whether or not to apply this workaround here in the ServerSentEventResult or have it be part of the default writing beahvior in the SseFormatter.
? jsonTypeInfo | ||
: jsonOptions.SerializerOptions.GetTypeInfo(typeof(object)); | ||
|
||
var json = JsonSerializer.Serialize(item.Data, typeInfo); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There must be a more performant way to do this.
@eiriktsarpalis
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JsonSerializer.SerializeToUtf8Bytes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slightly better since it avoids the string, but it still allocates a byte[] that we have to copy into IBufferWriter
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like SerializeToUtf8Bytes boils down to roughly
using var jsonWriter = new Utf8JsonWriter(writer, jsonOptions.SerializerOptions);
typeInfo.Write(jsonWriter, item.Data);
but I'm not familiar enough to be sure
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like SerializeToUtf8Bytes
/initializing a Utf8JsonWriter
around the IBufferWriter<byte>
might be as good as it gets here but I'd be curious to here what @eiriktsarpalis thinks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to utilize ReusableUtf8JsonWriter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would exposing JsonSerializer.Serialize
accepting IBufferWriter<byte>
overloads help in this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would exposing
JsonSerializer.Serialize
acceptingIBufferWriter<byte>
overloads help in this case?
Yep! I think that would be a nice solution.
{ | ||
ArgumentNullException.ThrowIfNull(httpContext); | ||
|
||
httpContext.Response.ContentType = "text/event-stream"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason not to disable buffering?
/// Strings serialized by this result type are serialized as raw strings without any additional formatting. | ||
/// </remarks> | ||
#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters | ||
public static IResult ServerSentEvents(IAsyncEnumerable<string> values, string? eventType = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Event types are values scoped to individual events, and not the entire stream. Given that the SseItem<T>
overload hardcodes to serialization, how can this API produce raw SSE events that includes other per-event fields such as event types?
I would guess that this was discussed during API review, but have you considered factoring into a separate method group (e.g. ServerSentEventsRaw
) that also accepts IAsyncEnumerable<SseItem<string>>
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind me, it seems the comment in line 1004 is addressing my concern.
/// <param name="eventType">The event type to be included in the HTTP response body.</param> | ||
/// <returns>The created <see cref="ServerSentEventsResult{T}"/> for the response.</returns> | ||
/// <remarks> | ||
/// Strings serialized by this result type are serialized as raw strings without any additional formatting. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to expose an option overriding this behavior?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We opted to expose fewer APIs for modifying the behavior of the formatter to start but I think we can pursue the proposal of adding an Action<Sse<T>, IBufferWriter>
argument to these overloads in the event we hear feedback on it.
|
||
// If the event type is string, we can skip JSON serialization | ||
// and directly use the SseFormatter's WriteAsync overload for strings. | ||
if (_events is IAsyncEnumerable<SseItem<string>> stringEvents) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about byte[]?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's useful in scenaria where you need to write raw UTF-8 data, but it might make more sense if we exposed that using the IBufferWriter callback as with the underlying library.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but it might make more sense if we exposed that using the IBufferWriter callback as with the underlying library.
This seems like a good proposal. In the meantime, we can add special handling for byte arrays in our FormatSseItem
callback.
9c83ed2
to
664aa6d
Compare
The implementation doesn't seem to have any keep-alive mechanism. For a general purpose usable implementation it should send a kind of keep-alive message if no user-provided message is sent within a specific timeframe (e.g. 5/10/20/30/tbd seconds). |
@ANahr Thanks for providing this feedback! I err on the side of keeping the API simpler here. It's possible for users to compose their own keep-alive message on a period timer on top of this API. The one gotcha is that there's no way to transmit comment messages (not complying with the SSE event format) with the current use of the |
Implements #56172.