[英]ASP.NET Core Disable Response Buffering
我正在嘗試將動態構建的大型 JSON 文件流式傳輸到客戶端(可能是 500 MB+)。 出於各種原因,我試圖禁用響應緩沖,但主要是為了提高內存效率。
我嘗試直接寫入HttpContext.Response.BodyWriter
但響應似乎在寫入輸出之前緩沖在內存中。 此方法的返回類型是Task
。
HttpContext.Response.ContentType = "application/json";
HttpContext.Response.ContentLength = null;
await HttpContext.Response.StartAsync(cancellationToken);
var bodyStream = HttpContext.Response.BodyWriter.AsStream(true);
await bodyStream.WriteAsync(Encoding.UTF8.GetBytes("["), cancellationToken);
await foreach (var item in cursor.WithCancellation(cancellationToken)
.ConfigureAwait(false))
{
await bodyStream.WriteAsync(JsonSerializer.SerializeToUtf8Bytes(item, DefaultSettings.JsonSerializerOptions), cancellationToken);
await bodyStream.WriteAsync(Encoding.UTF8.GetBytes(","), cancellationToken);
await bodyStream.FlushAsync(cancellationToken);
await Task.Delay(100,cancellationToken);
}
await bodyStream.WriteAsync(Encoding.UTF8.GetBytes("]"), cancellationToken);
bodyStream.Close();
await HttpContext.Response.CompleteAsync().ConfigureAwait(false);
注意:我意識到這段代碼非常hacky,試圖讓它工作,然后清理它
我正在使用Task.Delay
來驗證在本地測試時響應沒有被緩沖,因為我沒有完整的生產數據。 我也嘗試過IAsyncEnumerable
和yield return
,但失敗了,因為響應太大,Kestrel 認為可枚舉是無限的。
我試過了
HttpContext.Response.WriteAsync
編寫HttpContext.Response.BodyWriter.AsStream()
編寫HttpContext.Response.BodyWriter
編寫IApplicationBuilder.UseResponseCompression
的調用ContentType
之前禁用響應緩沖(因此在任何寫入響應之前)但沒有效果var responseBufferingFeature = context.Features.Get<IHttpResponseBodyFeature>();
responseBufferingFeature?.DisableBuffering();
這很簡單地重現了這個問題。 在調用response.CompleteAsync()
之前,客戶端不會收到任何數據。
[HttpGet]
[Route("stream")]
public async Task<EmptyResult> FileStream(CancellationToken cancellationToken)
{
var response = DisableResponseBuffering(HttpContext);
HttpContext.Response.Headers.Add("Content-Type", "application/gzip");
HttpContext.Response.Headers.Add("Content-Disposition", $"attachment; filename=\"player-data.csv.gz\"");
await response.StartAsync().ConfigureAwait(false);
var memory = response.Writer.GetMemory(1024*1024*10);
response.Writer.Advance(1024*1024*10);
await response.Writer.FlushAsync(cancellationToken).ConfigureAwait(false);
await Task.Delay(5000).ConfigureAwait(false);
var str2 = Encoding.UTF8.GetBytes("Bar!\r\n");
memory = response.Writer.GetMemory(str2.Length);
str2.CopyTo(memory);
response.Writer.Advance(str2.Length);
await response.CompleteAsync().ConfigureAwait(false);
return new EmptyResult();
}
private IHttpResponseBodyFeature DisableResponseBuffering(HttpContext context)
{
var responseBufferingFeature = context.Features.Get<IHttpResponseBodyFeature>();
responseBufferingFeature?.DisableBuffering();
return responseBufferingFeature;
}
嘗試禁用響應期貨的緩沖:
HttpContext.Features.Get<IHttpResponseBodyFeature>().DisableBuffering()
//As mentioned in documentation, to take effect, call it before any writes
並在Utf8JsonWriter
使用BodyWriter
以提高效率:
var pipe = context.HttpContext.Response.BodyWriter;
await pipe.WriteAsync(startArray);
using (var writer = new Utf8JsonWriter(pipe,
new JsonWriterOptions
{
Indented = option.WriteIndented,
Encoder = option.Encoder,
SkipValidation = true
}))
{
var dotSet = false;
foreach (var item in enumerable)
{
if (dotSet)
await pipe.WriteAsync(dot);
JsonSerializer.Serialize(writer, item, itemType, option);
await pipe.FlushAsync();
writer.Reset();
dotSet = true;
}
}
await pipe.WriteAsync(endArray);
就我而言,它給出了結果:在第一次請求后,總內存分配比 newcoreapp2.2 增加了 80% 以上,但沒有更多的內存泄漏。
對於那些仍然感興趣的人,這段代碼在使用 curl 時會立即發送數據:
public async Task Invoke(HttpContext context)
{
var g = context.Features.Get<IHttpResponseBodyFeature>();
g.DisableBuffering(); // doesn't seem to make a difference
context.Response.StatusCode = 200;
context.Response.ContentType = "text/plain; charset=utf-8";
//context.Response.ContentLength = null;
await g.StartAsync();
for (int i = 0; i < 10; ++i)
{
var line = $"this is line {i}\r\n";
var bytes = utf8.GetBytes(line);
// it seems context.Response.Body.WriteAsync() and
// context.Response.BodyWriter.WriteAsync() work exactly the same
await g.Writer.WriteAsync(new ReadOnlyMemory<byte>(bytes));
await g.Writer.FlushAsync();
await Task.Delay(1000);
}
await g.CompleteAsync();
}
我嘗試使用和不使用DisableBufering()
以及寫入管道( IHttpResponseBodyFeature.Writer
與HttpContext.Response.Body
)的變化似乎沒有區別。
在 curl 中,它會立即顯示消息,但是在 Chrome 和一些其他客戶端中,它會等待整個流顯示出來。
因此,我建議使用不等待整個流呈現它的客戶端來測試您的代碼行為。 另一個選項我仍在檢查aspnet core是否會在客戶端要求時自動選擇壓縮可能性,即使在管道中沒有配置壓縮。 所以我會推薦
使用 http.sys(使用 ASP.NET Core 6)時,我能夠做到這一點:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseHttpSys();
var app = builder.Build();
app.MapGet("/", async (context) =>
{
context.Response.StatusCode = 201;
await context.Response.StartAsync();
await context.Response.WriteAsync("x"); // client gets status code after this line
await context.Response.WriteAsync("Hello World!");
});
app.Run();
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.