[英]“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.