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.
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:
StatusCode cannot be set because the response has already started.
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. 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.
So what else tries to produce output in the pipeline given that Startup
class?
Edit: Stack trace, line 91 referenced at the end is the await next(context)
in the finally
block.
System.InvalidOperationException: StatusCode cannot be set because the response has already started. 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](IHttpApplication`1 application)
IMiddleware
in this case). learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/… – Deguzmannext
in this case. But circling back to the main question, what is executing when I callnext
given I haven't added anything else to the pipeline. Obviously ASP.NET itself does more under the hood -- but what would be trying to write in this scenario? Also, do you have any links that can clarify your earlier comment about replacing the stream? I appreciate the feedback. – Deguzmanawait next
withif(!context.Response.HasStarted)
since my fall-through case could theoretically be handled elsewhere. – Deguzman