4

I am trying to achieve a simple streaming (non buffered output) using really basic ASP.NET Core 6 app.

The following simple code should output the hello world text to the client and then close the connection (even by adding the document IHttpResponseBodyFeature option) :

app.MapGet("/a", async (ctx) =>
{
    var gg = ctx.Features.Get<Microsoft.AspNetCore.Http.Features.IHttpResponseBodyFeature>()!;
    gg.DisableBuffering();    
    await ctx.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("Hello, World!"));
    await ctx.Response.Body.FlushAsync();        
    await Task.Delay(2000);
});

This is of course a simple example of a behavior I am trying to achieve.

Simple curl request to the app can show that the hello world text is reaching to the client only after the 2 seconds wait.

On the .NET Framework the following code works as expected:

Response.Clear();
Response.Buffer = false;
Response.BufferOutput = false;
Response.Output.WriteLine("Hello World!");
Response.Output.Flush();
System.Threading.Tasks.Task.Delay(2000).Wait();
Response.End();

Simple curl request shows the "hello world" text shown immediately and after 2 seconds the connection get closed.

Thanks in advance.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Hagay Goshen
  • 599
  • 2
  • 13
  • Could you try to do `ctx.Response.Body.FlushAsync()` after `Task.Delay(2000);`? – D-Shih May 01 '22 at 13:05
  • Does this help? https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/generate-consume-asynchronous-stream – Neil May 01 '22 at 13:08
  • @D-Shih it doesnt help/work/changes the output. – Hagay Goshen May 01 '22 at 13:18
  • @Neil i saw similar documentations , i don't i see how it can help in the simple scenario i showed. – Hagay Goshen May 01 '22 at 13:19
  • Does it make a difference if you call [`CompleteAsync`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.httpresponse.completeasync?view=aspnetcore-6.0)? – ProgrammingLlama May 01 '22 at 13:31
  • @DiplomacyNotWar No change in ouput. – Hagay Goshen May 01 '22 at 13:36
  • I see. My other thought is that if you need to do stuff after a request response has been served, this might be an XY problem in that you should be handling that out-of-process work separately to the request, rather than trying to find a way to process it as part of the request. – ProgrammingLlama May 01 '22 at 13:37
  • @DiplomacyNotWar this is a simplest example i could produce to show that output buffer is still in effect , although i disabled it. I am pretty sure it should be work as simple as it was on .Net framework, just did not find a way yet... – Hagay Goshen May 01 '22 at 13:45
  • Isn't part of the problem that standard HTTP needs to know the complete payload to be able to fill in the content-length header. You can't just start streaming data with a normal kind of HTTP content and expect it to work. – Neil May 01 '22 at 13:49
  • @Neil Content-length is not mandatory , and you can just start streaming data. The problem is the response buffer that wont get disabled. If you need to stream 2gb of video while response buffer is on , you'll have to use 2gb of ram... – Hagay Goshen May 01 '22 at 13:52
  • Exactly, you are trying to use 'normal' HTTP context, but in a non-standard way, hence why it doesn't work. Just "turning off buffering" is not possible. This page https://blog.differentpla.net/blog/2012/07/14/streaming-http-responses-in-net/ suggests set ctx.Response.SendChunked = true, and Flush() the output stream after each chunk. – Neil May 01 '22 at 13:56
  • @Neil The simple code i supplied is what i would think of a straight forward approach to achieve non buffered behavior. I looked around at many examples of which most of them are outdated especially regarding to up to date asp.net 6. The headers returned by kestrel contain chunked transfer , and the ctx.Response.SendChunked option you proposed is not available in this code context. If you have a working code example i would be grateful. – Hagay Goshen May 02 '22 at 04:37

1 Answers1

2

After few more tries and searches i came upon the following issue on github :

https://github.com/dotnet/aspnetcore/issues/26565

Using the code there i managed to came up with a simple way for working non buffered output (also works on app.MapGet as shown on the question):

HttpContext.Response.StatusCode = 200;
await using var bodyStream = HttpContext.Response.BodyWriter.AsStream();
await HttpContext.Response.StartAsync();
for (var i = 0; i < 3; i++)
{
    await bodyStream.WriteAsync(Encoding.UTF8.GetBytes(Convert.ToBase64String(new byte[128 * 1])));
    await bodyStream.FlushAsync();
    await Task.Delay(2000);
}

await HttpContext.Response.CompleteAsync();
return Ok();

My app had some middle-ware which made debugging worse, disabling it made everything much simpler on my app/scenario.

Thanks for the comments , i hope this helps.

Hagay Goshen
  • 599
  • 2
  • 13
  • 1
    Seems to be broken in .NET 7.0 >.<... – Jens Dec 13 '22 at 11:58
  • 1
    Or perhaps not in .NET 7.0 it self, but rather in a development plugin o.O - more details here: https://github.com/dotnet/aspnetcore/issues/45037 – Jens Dec 13 '22 at 12:58
  • 1
    And help on disable the appropriate tools for it to work: https://stackoverflow.com/questions/69952420/how-to-disable-browser-link-in-asp-net-core-net-6-vs-2022 – Jens Dec 13 '22 at 13:03