Отклик уже запустил исключение в проекте промежуточного программного обеспечения ASP.NET Core.

Мой вопрос в основном таков - учитывая приведенный здесь код, что еще работает в конвейере, который пытается начать ответ клиенту? Мне известны другие вопросы об этом исключении, но похоже, что что-то в конвейере, которое запускается после, вызывает исключение мое промежуточное программное обеспечение - оно не вызвано моим промежуточным программным обеспечением, которое, как мне кажется, является разница в моем сценарии.

Это простой эхо-сервер ASP.NET Core 3.0 WebSocket - без SignalR, без MVC, без маршрутизации, без поддержки статических страниц и т. Д. Помимо обработки сокетов, когда промежуточное ПО видит запрос на text/html, оно отправляет обратно простая страница (жестко запрограммированная строка) в качестве эхо-клиента.

Браузер получает контент нормально, и мой обработчик исключений не запускается (критический момент), но после того, как мое промежуточное ПО завершит обработку запроса, ASP.NET Core регистрирует исключение:

StatusCode не может быть установлен, потому что ответ уже начался.

Код довольно минимален:

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);
        }
    }
}

Я думал, что проблема может быть в том, что я использую WriteAsync, но, похоже, это произойдет, если я также установлю статус HTTP в другом месте, без каких-либо других выходных данных, например, установка HTTP 500 в блоке catch. Если я перейду через специально созданное исключение, добавив throw в качестве самого первого оператора в промежуточном программном обеспечении, он попадет в finally, где возникает исключение «уже запущено».

Так что еще пытается произвести вывод в конвейере с учетом этого класса Startup?

Изменить: трассировка стека, строка 91, указанная в конце, - это await next(context) в блоке finally.

System.InvalidOperationException: StatusCode не может быть установлен, поскольку ответ уже начался. в Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ThrowResponseAlreadyStartedException (значение String) в Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.set_StatusCode (значение Int32sp) .Kestrel.Core.Internal.Http.HttpProtocol.Microsoft.AspNetCore.Http.Features.IHttpResponseFeature.set_StatusCode (значение Int32) в Microsoft.AspNetCore.Http.DefaultHttpResponse.set_StatusCode. > c.b__18_0 (контекст HttpContext) в KestrelWebSocketServer.WebSocketMiddleware.InvokeAsync (контекст HttpContext, далее RequestDelegate) в C: \ Source \ WebSocketExample \ KestrelWebSocketServer \ WebSocketExample \ KestrelWebSocketServer \ WebSocketMiddleware. ‹B__1> d.MoveNext () --- Конец трассировки стека из предыдущего места, где было сгенерировано исключение --- в Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtoc ol.ProcessRequests [TContext] (приложение IHttpApplication`1)


person McGuireV10    schedule 18.08.2019    source источник
comment
Один вопрос, почему вы регистрируете промежуточное ПО у поставщика услуг? Никогда раньше не видел, чтобы кто-то делал это. Вы пробовали удалить его из делегата configure-services? docs.microsoft.com/en- us / aspnet / core / basicals / middleware /   -  person alsami    schedule 18.08.2019
comment
Это то, что они называют промежуточным программным обеспечением, активируемым фабрикой. Я лично не люблю магические методы и предпочел бы, чтобы мое промежуточное ПО явно реализовывало интерфейс (IMiddleware в данном случае). docs.microsoft.com/en- us / aspnet / core / basicals / middleware /   -  person McGuireV10    schedule 18.08.2019
comment
После того, как вы написали в поток, другое промежуточное ПО не сможет. Вам необходимо заменить поток, если вы хотите записать и передать его по конвейеру.   -  person Nkosi    schedule 18.08.2019
comment
Почему ты вообще спускаешься вниз по течению. если вы пишете в ответ, разве вы не хотите замкнуть конвейер?   -  person Nkosi    schedule 18.08.2019
comment
@Nkosi Мне было интересно, не в этом ли проблема - в этом случае не должно быть никакого next. Но возвращаясь к основному вопросу, что выполняется, когда я вызываю next, учитывая, что я ничего не добавил в конвейер. Очевидно, что сам ASP.NET делает больше под капотом - но что будет пытаться писать в этом сценарии? Кроме того, есть ли у вас какие-либо ссылки, которые могут прояснить ваш предыдущий комментарий о замене потока? Я ценю обратную связь.   -  person McGuireV10    schedule 18.08.2019
comment
Фреймворк добавит свои собственные обработчики. В этом случае вам нужно изменить, когда вы будете звонить в следующий раз. Если вы пишете ответ, вы должны закончить его на этом и не вызывать следующего в конвейере.   -  person Nkosi    schedule 18.08.2019
comment
Полагаю, мне следует перед await next поставить if(!context.Response.HasStarted), поскольку мой случай провала теоретически можно было бы обработать где-нибудь в другом месте.   -  person McGuireV10    schedule 18.08.2019


Ответы (1)


Комментарий пользователя @Nkosi был правильным - когда мое промежуточное ПО способно полностью обработать запрос (либо обновив HTTP до WS, либо отправив обратно HTML-код эхо-клиента), он не должен передавать контекст делегату next. Однако в этом очень простом примере другие запросы (например, браузер пытается получить значок) мое промежуточное ПО ничего не делает, поэтому решение было следующим:

finally
{
    if(!context.Response.HasStarted)
        await next(context);
}
person Community    schedule 19.08.2019
comment
Это много раз упоминается в документах здесь как короткое замыкание. Также есть специальное предупреждение о том, что next.Invoke не следует вызывать после того, как ответ был отправлен клиенту. - person Nkosi; 19.08.2019