ASP.NET Web API 客户端断开连接时出现内部服务器错误

ASP.NET Web API Internal server error when client is disconnected

我们的 ASP.NET Web API 2 应用程序由移动应用程序使用。使用 Azure Application Insights,我们检测到许多响应代码为 500(内部服务器错误)的响应,在 Application Insights 中没有任何与之关联的异常。在调试会话期间,我们没有遇到任何异常抛出。

我们怀疑这可能是由客户端断开连接引起的。在实施模拟断开连接的 .net 应用程序后,我们甚至可以在干净的 webapi2 项目上重现此问题。

进一步调查发现,结果码500(客户端断开连接后)只有在满足特定条件时才会出现。需要在非 GET http 操作上以及在到达 ExecuteRequestHandler asp.net 事件之前取消请求。对于 GET 请求,我们无法复制此问题,无论是在进入或通过 ExecuteRequestHandler 事件的请求上。

我们的目标是从日志中过滤掉客户端断开连接并专注于实际问题。

此问题可能与 ASP.NET Web API OperationCanceledException when browser cancels the request 有关,但公认的解决方案不起作用,因为断开连接发生在到达任何 DelegatingHandler 之前。我们不会使用提到的解决方案,因为客户端断开连接不是服务器问题而是客户端问题。例如,在 netcore 中,取消的请求在日志中的响应代码为 0。无论如何,客户端将看不到结果,也看不到结果代码,因为它已经消失了。

其他possibly related question.

我们使用的是最新版本 Microsoft.AspNet.WebApi v5.2.7,调查仅在装有 IISExpress 的开发机器上进行。

更新

包括在干净的 webapi2 项目上重现的最少代码。

Global.asax.cs

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        GlobalConfiguration.Configure(WebApiConfig.Register);
    }

    protected void Application_EndRequest(object sender, EventArgs e)
    {
        var context = HttpContext.Current;
        Debug.WriteLine($"{context.Response.StatusCode}:{context.Response.SubStatusCode}:{context.Request.Path}:{context.Request.Url.AbsoluteUri}");
    }
}

HomeController.cs

[RoutePrefix("api/foo")]
public class HomeController : ApiController
{
    [Route("bar")]
    [HttpPut]
    public async Task<IHttpActionResult> Put()
    {
        await Task.Delay(200);
        return Ok();
    }
}

PUT http://localhost:56952/api/foo/bar

在 10 毫秒后取消时,平均 5 个取消的请求中有 1 个以 500 结尾。记录时,可以从 VS 中的 服务器日志 Application Insights 或输出 Window 访问响应代码。客户端将不会收到响应代码,因为在返回任何响应代码之前连接已关闭。

更新 2

来自 Application Insights 的遥测

我认为这是 .Net Framework 中的错误。我已经编写了一个变通方法来在满足指定条件时结束当前正在执行的请求:

  • 客户要求取消
  • 客户端未连接
  • 处理应该只发生在 ExecuteRequestHandler 事件
  • 之前
public class CancellationHttpModule : IHttpModule
{
    public void Init(HttpApplication application)
    {
        application.OnExecuteRequestStep(OnExecuteRequestStep);
    }

    private void OnExecuteRequestStep(HttpContextBase context, Action step)
    {
        if (context.Response.StatusCode != 0 
            && context.Response.ClientDisconnectedToken.IsCancellationRequested
            && !context.Response.IsClientConnected
            && (int)context.CurrentNotification < (int)RequestNotification.ExecuteRequestHandler)
        {

            context.Response.StatusCode = 0;
            context.ApplicationInstance.CompleteRequest();
        }
        step();
    }


    public void Dispose()
    {
    }
}

处理发生后,状态代码更改为零(就像在 .net 核心中一样)并且 ASP.NET 管道中的 skipping all remaining events 变为 EndRequest 事件。我已经在生产环境中测试了这个实现一个月,没有发现导致 500 的请求在日志中没有相应的异常记录。