Я пытаюсь передать большой файл JSON, созданный на лету, клиенту (может быть 500 МБ +). Я пытаюсь отключить буферизацию ответов по разным причинам, хотя в основном из-за эффективности памяти.
Я пытался записать напрямую в 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);
Примечание. Я понимаю, что этот код очень хакерский, пытаюсь заставить его работать, а затем очищаю его
Я использую Task.Delay
для проверки того, что ответ не буферизуется при локальном тестировании, поскольку у меня нет полных производственных данных. Я также пробовал IAsyncEnumerable
и yield return
, но это не помогло, потому что ответ настолько велик, что Кестрел считает, что перечисляемое бесконечно.
Я пробовал
- Установка KestrelServerLimits.MaxResponseBufferSize для небольшого числа, даже 0;
- Запись с
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;
}