简体   繁体   English

准ASP.NET Core中间件项目中的“响应已经开始”异常

[英]“Response has already started” exception in a bare-bones ASP.NET Core middleware project

My question is basically this -- given the code shown here, what else runs in the pipeline that is trying to start a response to the client? 我的问题基本上是这样的-给定此处显示的代码,试图启动对客户端响应的管道中还有哪些其他内容? I'm aware of the other questions about that exception, but it seems something in the pipeline which runs after my middleware is causing the exception -- it isn't caused by my middleware, which I think is the difference in my scenario. 我知道有关该异常的其他问题,但似乎在中间件引起异常之后运行的管道中有一些东西–它不是由中间件引起的,我认为这是我的情况的与众不同。

This is a bare-bones ASP.NET Core 3.0 WebSocket echo server -- no SignalR, no MVC, no routing, no static page support, etc. Apart from handling the sockets, when the middleware sees a request for text/html it sends back a simple page (a hard-coded string) as the echo client. 这是一个准系统的ASP.NET Core 3.0 WebSocket回显服务器-没有SignalR,没有MVC,没有路由,没有静态页面支持等。除了处理套接字,当中间件看到发送的text/html请求时返回一个简单页面(硬编码字符串)作为回显客户端。

The browser receives the content just fine, and my exception handler is not triggered (a crucial point), but after my middleware is done processing the request, ASP.NET Core logs the exception: 浏览器可以很好地接收内容,并且不会触发我的异常处理程序(关键点),但是在我的中间件处理完请求之后,ASP.NET Core记录了异常:

StatusCode cannot be set because the response has already started. 无法设置StatusCode,因为响应已经开始。

The code is quite minimal: 代码非常简单:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient<WebSocketMiddleware>();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        var webSocketOptions = new WebSocketOptions()
        {
            KeepAliveInterval = TimeSpan.FromSeconds(120),
            ReceiveBufferSize = 4 * 1024
        };
        app.UseWebSockets(webSocketOptions);
        app.UseMiddleware<WebSocketMiddleware>();
    }
}
public class WebSocketMiddleware : IMiddleware
{
    // fields/properties omitted
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        try
        {
            if (context.WebSockets.IsWebSocketRequest)
            {
                // omitted, socket upgrade works normally
            }
            else
            {
                if(context.Request.Headers["Accept"][0].Contains("text/html"))
                {
                    // this works but causes the exception later in the pipeline
                    await context.Response.WriteAsync(SimpleHtmlClient.HTML);
                }
                else
                {
                    // ignore other requests such as favicon
                }
            }
        }
        catch (Exception ex)
        { 
            // code omitted, never triggered 
        }
        finally
        {
            // exception happens here
            await next(context);
        }
    }
}

I thought the problem might be my use of WriteAsync but it seems to happen if I also set the HTTP status elsewhere, with no other output, like setting HTTP 500 in the catch block. 我以为问题可能出在我对WriteAsync使用上,但是如果我还在其他位置设置HTTP状态而没有其他输出(例如在catch块中设置HTTP 500),这似乎会发生。 If I step through a purposely-caused exception by adding a throw as the very first statement in the middleware, it gets to the finally where the "already started" exception occurs. 如果我通过在中间件中的第一个语句中添加一个throw作为逐步处理故意导致的异常,那么它将到达发生“已经开始”异常的finally位置。

So what else tries to produce output in the pipeline given that Startup class? 那么给定Startup类,还有什么尝试在管道中产生输出呢?

Edit: Stack trace, line 91 referenced at the end is the await next(context) in the finally block. 编辑:堆栈跟踪,最后引用的第91行是finally块中的await next(context)

System.InvalidOperationException: StatusCode cannot be set because the response has already started. System.InvalidOperationException:无法设置StatusCode,因为响应已经开始。 at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ThrowResponseAlreadyStartedException(String value) at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.set_StatusCode(Int32 value) at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.Microsoft.AspNetCore.Http.Features.IHttpResponseFeature.set_StatusCode(Int32 value) at Microsoft.AspNetCore.Http.DefaultHttpResponse.set_StatusCode(Int32 value) at Microsoft.AspNetCore.Builder.ApplicationBuilder.<>c.b__18_0(HttpContext context) at KestrelWebSocketServer.WebSocketMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) in C:\\Source\\WebSocketExample\\KestrelWebSocketServer\\WebSocketMiddleware.cs:line 91 at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass5_1.<b__1>d.MoveNext() --- End of stack trace from previous location where exception was thrown --- at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApp 在Microsoft.AspNetCore.Server处Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.set_StatusCode(Int32值)处Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ThrowResponseAlreadyStartedException(字符串值) .Kestrel.Core.Internal.Http.HttpProtocol.Microsoft.AspNetCore.Http.Features.IHttpResponseFeature.set_StatusCode(Int32值)在Microsoft.AspNetCore.Http.DefaultHttpResponse.set_StatusCode(Int32值)在Microsoft.AspNetCore.Builder.ApplicationBuilder。< > c.b__18_0(HttpContext上下文)在KestrelWebSocketServer.WebSocketMiddleware.InvokeAsync(HttpContext上下文,RequestDelegate接下来)在Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.C:\\ Source \\ WebSocketExample \\ KestrelWebSocketServer \\ WebSocketMiddleware.cs:第91行。<> c__DisplayClass5_1。 <b__1> d.MoveNext()---从上一个引发异常的位置开始的堆栈跟踪-在Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests [TContext](IHttpApp lication`1 application) lication`1申请)

The comment from user @Nkosi was correct -- when my middleware is able to handle the request fully (either upgrading HTTP to WS, or sending back the echo client HTML) it should not hand off context to the next delegate. 用户@Nkosi的评论是正确的-当我的中间件能够完全处理请求(将HTTP升级到WS或发送回显客户端HTML)时,它不应将上下文传递给next委托。 However, in this very simple example other requests (such as a browser trying to retrieve favicon) my middleware doesn't do anything, so the solution was this: 但是,在这个非常简单的示例中,我的中间件没有执行任何其他请求(例如尝试检索Favicon的浏览器),因此解决方案是:

finally
{
    if(!context.Response.HasStarted)
        await next(context);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 最小的占用空间/裸机ASP.NET核心WebAPI - Minimal Footprint / Bare-bones ASP.NET Core WebAPI ASP.Net Core 中间件无法设置异常状态码,因为“响应已经开始” - ASP.Net Core middleware cannot set status code on exception because "response has already started" ASP.Net core JWT Bearer authentication with Keycloak StatusCode 无法设置,因为响应已经开始 - ASP.Net core JWT Bearer authentication with Keycloak StatusCode cannot be set because the response has already started ado.net实体的准系统浅序列化 - Bare-bones shallow serialization of ado.net entity 在例外的asp.net核心中间件上获得空响应 - Getting empty response on asp.net core middleware on exception 无法创建 cookie,在 asp.net 核心(剃须刀页面)中获取“标题是只读的,响应已经开始” - Can't create cookie, getting "Headers are read-only, response has already started" in asp.net core (razor pages) ASP.Net Core 异常处理中间件 - ASP.Net Core exception handling middleware 重写响应体 asp.net 核心客户中间件 - re writing response body asp.net core custome middleware ASP.NET内核中响应缓存中间件的使用和好处 - The use and benefits of Response Caching Middleware in ASP.NET Core 向 ASP.NET Core 中间件添加响应头 - Add Response Headers to ASP.NET Core Middleware
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM