dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.

Home Page:https://asp.net

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Buffering issues when returning an SSE response

kikaragyozov opened this issue · comments

In the controller I'm basically returning a server-sent event by manually setting the content type, starting the response with Response.StartAsync(cancellationToken) and then directly writing data to Response.Body, finishing with a Response.CompleteAsync(). For additional context - another service is returning the SSE in a Stream, so I'm effectively just calling Stream.CopyToAsync(Response.Body, cancellationToken)

The issue I'm facing is that when the above code runs on Kestrel, the events in the stream are immediately returned and it seems like no buffering is being done.

The moment I switch to IIS or IIS Express though, something weird is going on as the client receives the response after writing to the response body has finished.

After investigating, I thought the Dynamic Response Caching module for IIS was at fault here. But it turns out that it was actually not installed on the system as shown here:
image

If Dynamic Response Caching is not involved, what else is going on that would screw up the SSE like that? If I have to follow common sense, does the above mean that:

  1. By default, Kestrel is configured to NOT buffer the response body. (How do you control this?)
  2. By default, IIS is configured to buffer the response body. (How do you control this?)

I find it weird that Kestrel would have no response body buffering by default. Is this really the case? If not, what's going on here? Am I perhaps implementing SSE in a weird way?

Calling HttpContext!.Features.Get<IHttpResponseBodyFeature>()!.DisableBuffering() fixes the issue when running under IIS/IIS Express.

.NET Version: 8.0.300-preview.24203.14

Calling HttpContext!.Features.Get<IHttpResponseBodyFeature>()!.DisableBuffering() fixes the issue when running under IIS/IIS Express.

That's expected, SignalR's SSE implementation has to do the same. Are you still having issues, or are you just trying to understand what component was causing the problem?

Calling HttpContext!.Features.Get<IHttpResponseBodyFeature>()!.DisableBuffering() fixes the issue when running under IIS/IIS Express.

That's expected, SignalR's SSE implementation has to do the same. Are you still having issues, or are you just trying to understand what component was causing the problem?

I'm just trying to understand how things work under the hood, especially when switching between Kestrel and IIS. It seems that when running under Kestrel, there's no buffering involved? The SSE stream is being returned in chunks, unlike in IIS where everything's returned at once.

Here's the implementation for IIS:

void IHttpResponseBodyFeature.DisableBuffering()
{
NativeMethods.HttpDisableBuffering(_requestNativeHandle);
DisableCompression();
}
private void DisableCompression()
{
var serverVariableFeature = (IServerVariablesFeature)this;
serverVariableFeature["IIS_EnableDynamicCompression"] = "0";
}

Which calls https://learn.microsoft.com/en-us/iis/web-development-reference/native-code-api-reference/ihttpresponse-disablebuffering-method

Kestrel we control the entire stack and there's not explicit buffering (there's a "write behind buffer", but it's flushed immediately).

Here's the implementation for IIS:

void IHttpResponseBodyFeature.DisableBuffering()
{
NativeMethods.HttpDisableBuffering(_requestNativeHandle);
DisableCompression();
}
private void DisableCompression()
{
var serverVariableFeature = (IServerVariablesFeature)this;
serverVariableFeature["IIS_EnableDynamicCompression"] = "0";
}

Which calls https://learn.microsoft.com/en-us/iis/web-development-reference/native-code-api-reference/ihttpresponse-disablebuffering-method

Kestrel we control the entire stack and there's not explicit buffering (there's a "write behind buffer", but it's flushed immediately).

Thank you @davidfowl for the information! This is what I needed. Am I correct in understanding that IIS buffering cannot be disabled from IIS? Compression can be though?

And finally - can one control how buffering is done in IIS, like tuning the buffer size for example?

In Kestrel, there's KestrelServerLimits.MaxResponseBufferSize - How does that come into the picture?

Thank you @davidfowl for the information! This is what I needed. Am I correct in understanding that IIS buffering cannot be disabled from IIS? Compression can be though?

That's right. AFAIK you have to call this API.

In Kestrel, there's KestrelServerLimits.MaxResponseBufferSize - How does that come into the picture?

It doesn't. This is about internal buffer sizes, not the operation of buffering content.

Closing this as I got what I was looking for. Thanks for the clarifications!

If anyone wouldn't mind sharing internally how IIS response buffering works, that'd be cool 👍