ASP.NET Core 5-输出虚幻数据的超时中间件



我创建了一个超时中间件,其工作原理基本上如下:

public async Task InvokeAsync(HttpContext httpContext)
{
var stopwatch = Stopwatch.StartNew();    
using (var timeoutTS = CancellationTokenSource.CreateLinkedTokenSource(httpContext.RequestAborted))
{
var delayTask = Task.Delay(config.Timeout);

var res = await Task.WhenAny(delayTask, _next(httpContext));
Trace.WriteLine("Time taken = " + stopwatch.ElapsedMilliseconds);
if (res == delayTask)
{
timeoutTS.Cancel();
httpContext.Response.StatusCode = 408;
}
}
}

为了测试它,我创建了一个控制器动作:

[HttpGet]
public async Task<string> Get(string timeout)
{
var result = DateTime.Now.ToString("mm:ss.fff");
if (timeout != null)
{
await Task.Delay(2000);
}
var rng = new Random();
result = result + " - " + DateTime.Now.ToString("mm:ss.fff");
return result;
}

配置的超时为500ms,报告的Time Taken通常为501-504ms(这是一个非常可接受的打滑(。

问题是,我不时在输出窗口上看到一个错误,说响应已经启动。我心想:这不可能!这比相应控制器上的CCD_ 2的结束早1秒发生。

所以我打开fiddler,(令我惊讶的是(几个请求在1.3-1.7秒内返回,并给出了完整的响应。

通过将写在响应体上的报告时间与fiddler"上的时间戳进行比较;统计量";选项卡我可以保证我看到的回复不属于手头的请求!

有人知道发生了什么事吗?为什么这是";搅乱";发生

坦率地说,您并没有按照中间件的设计方式使用中间件。

您可能想阅读这些中间件文档。

ASP.NET Core请求管道由一系列请求委托组成,这些委托一个接一个地调用。

在您的情况下,您的中间件与下一个中间件并行运行。

当中间件短路时,它被称为终端中间件,因为它阻止了进一步的中间件处理请求。

如果我理解正确,您可能想创建这样的终端中间件,但很明显,您目前的中间件不是。

在您的案例中,您已经调用了_next中间件,这意味着请求已经移交给请求管道中的下一个中间件。后续中间件组件可以在超时之前启动响应。即中间件和后续中间件之间的竞争条件。

为避免出现竞争情况,在分配状态代码之前,应始终检查HasStarted。如果响应已经启动,您所能做的可能只是在不希望客户端等待太久的情况下中止请求。

static void ResetOrAbort(HttpContext httpContext)
{
var resetFeature = httpContext.Features.Get<IHttpResetFeature>();
if (resetFeature is not null)
{
resetFeature.Reset(2);
}
else
{
httpContext.Abort();
}
}
app.Use(next =>
{
return async context =>
{
var nextTask = next(context);
var t = await Task.WhenAny(nextTask, Task.Delay(100));
if (t != nextTask)
{
var response = context.Response;
// If response has not started, return 408
if (!response.HasStarted)
{
// NOTE: you will still get same exception
// because the above check does not eliminate
// any race condition
try
{
response.StatusCode = StatusCodes.Status408RequestTimeout;
await response.StartAsync();
}
catch
{
ResetOrAbort(context);
}
}
// Otherwise, abort the request
else
{
ResetOrAbort(context);
}
}
};
});

最新更新