ASP.NET 具有无限输出的核心中间件



我有一个 ASP.NET 核心中间件,当它接管处理请求时会产生无限量的数据。它进入一个循环,只要允许,它就会调用await context.Response.Body.WriteAsync。如果调用方保持连接,则它希望继续接收数据。该服务由 Kestrel 托管,并且似乎如目前所述正常工作。但是,我发现当调用方断开连接时,Kestrel 似乎没有注意到并继续从中间件抽取输出。该输出不会去任何地方,因为进程的内存使用量不会增加,同时netstat -an不再显示连接。但是,中间件一直在嘀咕

。对于典型的HTTP请求,这不是一个非常严重的问题,因为大多数情况下,当客户端只读取了部分请求时,它不会断开连接,而在那些这样做的情况下,响应的大小无论如何都是有限的。但是此终结点的模式是,数据在概念上长度是无限的,并且调用方只要愿意就保持连接,然后通过断开连接来发出不再需要更多数据的信号。

这些图像说明了这个问题:

https://i.stack.imgur.com/Hw2iD.jpg

如何使中间件在客户端断开连接时注意到?

我也在 GitHub 存储库aspnetcore上将其作为一个问题发布,我得到了一个回复,解释了问题并提供了解决方案:

  • https://github.com/dotnet/aspnetcore/issues/22156

基本上,无论好坏,ASP.NET Core 基础结构都会抑制写入操作中的所有错误,因此,如果请求已中止,调用context.Response.Body.WriteAsync将无提示失败。我个人认为某处存在错误,但给出的理由是,这可以减少服务器无法控制的行为中的异常/日志垃圾邮件。

因此,如果您正在编写像我这样的循环,则必须显式检查请求是否已中止。上下文提供用于捕获中止的CancellationToken。还可以将此CancellationToken用于处理程序正在执行的其他操作,这些操作不在请求上下文范围内。

我的数据泵现在如下所示:

while (true)
{
int bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length, context.RequestAborted);
if (bytesRead < 0)
throw new Exception("I/O error");
if (bytesRead == 0)
break;
await context.Response.Body.WriteAsync(buffer, 0, bytesRead);
if (context.RequestAborted.IsCancellationRequested)
break;
_statusConsoleSender.NotifyRequestProgress(requestID, bytesRead);
}