Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UseExceptionHandler cannot handle returning a 404 error when not running in IIS Express without causing an HTTP protocol error #31024

Closed
RyanClementsHax opened this issue Mar 18, 2021 · 10 comments · Fixed by #31142
Assignees
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions bug This issue describes a behavior which is not expected - a bug.
Milestone

Comments

@RyanClementsHax
Copy link

Describe the bug

When returning a 404 within UseExceptionHandler when the api is not running in IIS Express, http protocol errors happen. There are no issues when running in IIS Express.

To Reproduce

Here is an example repo that reliably reproduces this error.
Use a handler like the following:

app.UseExceptionHandler(builder =>
    builder.Run(async context =>
    {
        var errorFeature = context.Features.Get<IExceptionHandlerFeature>();
        var ex = errorFeature.Error;
    
        context.Response.StatusCode = ex switch
        {
            DomainException domainEx => StatusCodes.Status400BadRequest,
            NotFoundException notFoundEx => StatusCodes.Status404NotFound, // if you change this status code to anything else, it will be fine
            _ => StatusCodes.Status500InternalServerError,
        };
    
        await context.Response.WriteAsync("There was an error caught in the exception handler lambda");
    }));

Then use a controller like the following

[ApiController]
public class NotFoundController : ControllerBase
{
    /// <summary>
    /// This endpoint works fine and returns a 400 as expected in all cases
    /// </summary>
    /// <returns></returns>
    [HttpGet("throw-domain-ex")]
    public IActionResult ThrowDomainEx() => throw new DomainException();

    /// <summary>
    /// This endpoint returns a 404 in IIS Express and causes http protocol errors otherwise.
    /// Chrome logs this as "Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR".
    /// Trying to call the endpoint when it isn't running in IIS Express with something like HttpClient will cause: System.Net.Http.HttpRequestException: Error while copying content to a stream. ---> System.IO.IOException:  ---> NotFoundBugExample.NotFoundBugExample
    /// </summary>
    /// <returns></returns>
    [HttpGet("throw-not-found")]
    public IActionResult ThrowNotFound() => throw new NotFoundException();

    /// <summary>
    /// This endpoint works fine and returns a 404 as expected in all cases
    /// </summary>
    /// <returns></returns>
    [HttpGet("return-not-found")]
    public IActionResult ReturnNotFound() => NotFound();
}

Lastly, run the app NOT in IIS Express and hit the endpoint that causes the middleware to set the status code to 404. In this example, that is /throw-not-found.
It should also be noted that setting the response code to anything else in the middleware circumvents the issue.
It should also be noted that using normal middleware works fine. So replacing the aforementioned middleware with the one below will fix the issue.

app.Use(async (context, next) =>
{
    try
    {
        await next();
    }
    catch (Exception ex)
    {
        context.Response.StatusCode = ex switch
        {
            DomainException => StatusCodes.Status400BadRequest,
            // if you change this status code to anything else, it will be fine
            NotFoundException => StatusCodes.Status404NotFound,
            _ => StatusCodes.Status500InternalServerError,
        };

        await context.Response.WriteAsync("There was an error caught in the exception handler lambda");
    }
});

Exceptions (if any)

This is what the chrome dev tools logs when I hit this endpoint using Swagger UI
image

Hitting the endpoint with something like HttpClient will cause the following error: System.Net.Http.HttpRequestException: Error while copying content to a stream. ---> System.IO.IOException: ---> NotFoundBugExample.NotFoundBugExample

Further technical details

  • ASP.NET Core version: net5.0
  • Include the output of dotnet --info
$ dotnet --info
.NET SDK (reflecting any global.json):
 Version:   5.0.201
 Commit:    a09bd5c86c

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.19042
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\5.0.201\

Host (useful for support):
  Version: 5.0.4
  Commit:  f27d337295

.NET SDKs installed:
  2.1.202 [C:\Program Files\dotnet\sdk]
  2.1.522 [C:\Program Files\dotnet\sdk]
  3.1.407 [C:\Program Files\dotnet\sdk]
  5.0.103 [C:\Program Files\dotnet\sdk]
  5.0.104 [C:\Program Files\dotnet\sdk]
  5.0.201 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.All 2.1.26 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.26 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 3.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 2.0.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.26 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.1.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 3.1.13 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

To install additional .NET runtimes or SDKs:
  https://aka.ms/dotnet-download
  • IDE: Visual Studio 16.9.1
@Tratcher
Copy link
Member

Hitting the endpoint with something like HttpClient will cause the following error: System.Net.Http.HttpRequestException: Error while copying content to a stream. ---> System.IO.IOException: ---> NotFoundBugExample.NotFoundBugExample

That looks like it came from TestServer rather than a real HttpClient instance?

The protocol error may be related to #24502, where Kestrel didn't used to filter out headers that were prohibited by HTTP/2. That said, the exception handler only sets cache headers which should be allowed:

headers[HeaderNames.CacheControl] = "no-cache,no-store";
headers[HeaderNames.Pragma] = "no-cache";
headers[HeaderNames.Expires] = "-1";
headers.Remove(HeaderNames.ETag);

Try turning on kestrel's connection logger to capture the response headers:
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel/endpoints?view=aspnetcore-5.0#connection-logging

@RyanClementsHax
Copy link
Author

You are correct that the httpclient exception was coming from one configured from a test server. I tried hitting this endpoint in an integration test (which is actually how I found the error to begin with). The code below is how the server and httpclient was set up in case you are curious. Its nothing special.

_factory = new TestWebApplicationFactory<Startup>();
_client = _factory.CreateClient();

As requested, though, here are the logs from doing what you have requested. I also put the log level to trace for added help. I have also added the code used to produce this on the kestrel-connection-logging branch in the same repo.

When NOT running in IIS Express:

info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 GET https://localhost:5003/throw-not-found - -
trce: Microsoft.AspNetCore.Server.Kestrel[37]
      Connection id "0HM7A921DEUS0" received SETTINGS frame for stream ID 0 with length 0 and flags ACK
trce: Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware[2]
      All hosts are allowed.
dbug: Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware[3]
      The request path  does not match the path filter
dbug: Microsoft.AspNetCore.Routing.Matching.DfaMatcher[1001]
      1 candidate(s) found for the request path '/throw-not-found'
dbug: Microsoft.AspNetCore.Routing.Matching.DfaMatcher[1005]
      Endpoint 'NotFoundBugExample.Controllers.NotFoundController.ThrowNotFound (NotFoundBugExample)' with route pattern 'throw-not-found' is valid for the request path '/throw-not-found'
dbug: Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware[1]
      Request matched endpoint 'NotFoundBugExample.Controllers.NotFoundController.ThrowNotFound (NotFoundBugExample)'
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
      Executing endpoint 'NotFoundBugExample.Controllers.NotFoundController.ThrowNotFound (NotFoundBugExample)'
dbug: Microsoft.AspNetCore.Mvc.ModelBinding.ModelBinderFactory[12]
      Registered model binder providers, in the following order: Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BinderTypeModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ServicesModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.HeaderModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.FloatingPointTypeModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.EnumTypeModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.DateTimeModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.SimpleTypeModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.CancellationTokenModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ByteArrayModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.FormFileModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.FormCollectionModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.KeyValuePairModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.DictionaryModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ArrayModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.CollectionModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexObjectModelBinderProvider
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[3]
      Route matched with {action = "ThrowNotFound", controller = "NotFound"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.IActionResult ThrowNotFound() on controller NotFoundBugExample.Controllers.NotFoundController (NotFoundBugExample).
dbug: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[1]
      Execution plan of authorization filters (in the following order): None
dbug: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[1]
      Execution plan of resource filters (in the following order): None
dbug: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[1]
      Execution plan of action filters (in the following order): Microsoft.AspNetCore.Mvc.ModelBinding.UnsupportedContentTypeFilter (Order: -3000), Microsoft.AspNetCore.Mvc.Infrastructure.ModelStateInvalidFilter (Order: -2000)
dbug: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[1]
      Execution plan of exception filters (in the following order): None
dbug: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[1]
      Execution plan of result filters (in the following order): Microsoft.AspNetCore.Mvc.Infrastructure.ClientErrorResultFilter (Order: -2000)
dbug: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[1]
      Executing controller factory for controller NotFoundBugExample.Controllers.NotFoundController (NotFoundBugExample)
dbug: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2]
      Executed controller factory for controller NotFoundBugExample.Controllers.NotFoundController (NotFoundBugExample)
trce: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2]
      Action Filter: Before executing OnActionExecuting on filter Microsoft.AspNetCore.Mvc.ModelBinding.UnsupportedContentTypeFilter.
trce: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[3]
      Action Filter: After executing OnActionExecuting on filter Microsoft.AspNetCore.Mvc.ModelBinding.UnsupportedContentTypeFilter.
trce: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2]
      Action Filter: Before executing OnActionExecuting on filter Microsoft.AspNetCore.Mvc.Infrastructure.ModelStateInvalidFilter.
trce: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[3]
      Action Filter: After executing OnActionExecuting on filter Microsoft.AspNetCore.Mvc.Infrastructure.ModelStateInvalidFilter.
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[1]
      Executing action method NotFoundBugExample.Controllers.NotFoundController.ThrowNotFound (NotFoundBugExample) - Validation state: Valid
trce: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2]
      Action Filter: Before executing OnActionExecuted on filter Microsoft.AspNetCore.Mvc.Infrastructure.ModelStateInvalidFilter.
trce: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[3]
      Action Filter: After executing OnActionExecuted on filter Microsoft.AspNetCore.Mvc.Infrastructure.ModelStateInvalidFilter.
trce: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2]
      Action Filter: Before executing OnActionExecuted on filter Microsoft.AspNetCore.Mvc.ModelBinding.UnsupportedContentTypeFilter.
trce: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[3]
      Action Filter: After executing OnActionExecuted on filter Microsoft.AspNetCore.Mvc.ModelBinding.UnsupportedContentTypeFilter.
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2]
      Executed action NotFoundBugExample.Controllers.NotFoundController.ThrowNotFound (NotFoundBugExample) in 2749.7385ms
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
      Executed endpoint 'NotFoundBugExample.Controllers.NotFoundController.ThrowNotFound (NotFoundBugExample)'
fail: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[1]
      An unhandled exception has occurred while executing the request.
      NotFoundBugExample.NotFoundException: Exception of type 'NotFoundBugExample.NotFoundException' was thrown.
         at NotFoundBugExample.Controllers.NotFoundController.ThrowNotFound() in C:\Users\rclements\source\repos\NotFoundBugExample\NotFoundBugExample\Controllers\NotFoundController.cs:line 22
         at lambda_method2(Closure , Object , Object[] )
         at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
         at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
         at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
trce: Microsoft.AspNetCore.Server.Kestrel[37]
      Connection id "0HM7A921DEUS0" sending HEADERS frame for stream ID 1 with length 81 and flags END_HEADERS
trce: Microsoft.AspNetCore.Server.Kestrel[37]
      Connection id "0HM7A921DEUS0" sending DATA frame for stream ID 1 with length 57 and flags NONE
dbug: Microsoft.AspNetCore.Server.Kestrel.Core.Internal.LoggingConnectionMiddleware[0]
      WriteAsync[156]
      00 00 51 01 04 00 00 00  01 8D 58 11 6E 6F 2D 63   ..Q..... ..X.no-c
      61 63 68 65 2C 6E 6F 2D  73 74 6F 72 65 61 1D 54   ache,no- storea.T
      68 75 2C 20 31 38 20 4D  61 72 20 32 30 32 31 20   hu, 18 M ar 2021
      32 30 3A 30 30 3A 35 37  20 47 4D 54 40 06 70 72   20:00:57  GMT@.pr
      61 67 6D 61 08 6E 6F 2D  63 61 63 68 65 64 02 2D   agma.no- cached.-
      31 76 07 4B 65 73 74 72  65 6C 00 00 39 00 00 00   1v.Kestr el..9...
      00 00 01 54 68 65 72 65  20 77 61 73 20 61 6E 20   ...There  was an
      65 72 72 6F 72 20 63 61  75 67 68 74 20 69 6E 20   error ca ught in
      74 68 65 20 65 78 63 65  70 74 69 6F 6E 20 68 61   the exce ption ha
      6E 64 6C 65 72 20 6C 61  6D 62 64 61            ndler la mbda
warn: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[4]
      No exception handler was found, rethrowing original exception.
fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
      NotFoundBugExample.NotFoundException: Exception of type 'NotFoundBugExample.NotFoundException' was thrown.
         at NotFoundBugExample.Controllers.NotFoundController.ThrowNotFound() in C:\Users\rclements\source\repos\NotFoundBugExample\NotFoundBugExample\Controllers\NotFoundController.cs:line 22
         at lambda_method2(Closure , Object , Object[] )
         at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
         at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
         at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
         at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.HandleException(HttpContext context, ExceptionDispatchInfo edi)
         at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
         at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
         at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
         at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
         at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
warn: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[2]
      The response has already started, the error page middleware will not be executed.
fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HM7A921DEUS0", Request id "0HM7A921DEUS0:00000001": An unhandled exception was thrown by the application.
      NotFoundBugExample.NotFoundException: Exception of type 'NotFoundBugExample.NotFoundException' was thrown.
         at NotFoundBugExample.Controllers.NotFoundController.ThrowNotFound() in C:\Users\rclements\source\repos\NotFoundBugExample\NotFoundBugExample\Controllers\NotFoundController.cs:line 22
         at lambda_method2(Closure , Object , Object[] )
         at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
         at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
         at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
         at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.HandleException(HttpContext context, ExceptionDispatchInfo edi)
         at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
         at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
         at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
         at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
         at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)
dbug: Microsoft.AspNetCore.Server.Kestrel[35]
      Trace id "0HM7A921DEUS0:00000001": HTTP/2 stream error "INTERNAL_ERROR". A Reset is being sent to the stream.
      Microsoft.AspNetCore.Connections.ConnectionAbortedException: An error occurred after the response headers were sent, a reset is being sent.
trce: Microsoft.AspNetCore.Server.Kestrel[37]
      Connection id "0HM7A921DEUS0" sending RST_STREAM frame for stream ID 1 with length 4 and flags 0x0
dbug: Microsoft.AspNetCore.Server.Kestrel.Core.Internal.LoggingConnectionMiddleware[0]
      WriteAsync[13]
      00 00 04 03 00 00 00 00  01 00 00 00 02         ........ .....
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished HTTP/2 GET https://localhost:5003/throw-not-found - - - 404 - - 3501.1645ms

(If I did this incorrectly, let me know)

@Tratcher
Copy link
Member

Ok, it doesn't look like a headers issue, I only see the expected headers here:

      00 00 51 01 04 00 00 00  01 8D 58 11 6E 6F 2D 63   ..Q..... ..X.no-c
      61 63 68 65 2C 6E 6F 2D  73 74 6F 72 65 61 1D 54   ache,no- storea.T
      68 75 2C 20 31 38 20 4D  61 72 20 32 30 32 31 20   hu, 18 M ar 2021
      32 30 3A 30 30 3A 35 37  20 47 4D 54 40 06 70 72   20:00:57  GMT@.pr
      61 67 6D 61 08 6E 6F 2D  63 61 63 68 65 64 02 2D   agma.no- cached.-
      31 76 07 4B 65 73 74 72  65 6C 00 00 39 00 00 00   1v.Kestr el..9...
      00 00 01 54 68 65 72 65  20 77 61 73 20 61 6E 20   ...There  was an
      65 72 72 6F 72 20 63 61  75 67 68 74 20 69 6E 20   error ca ught in
      74 68 65 20 65 78 63 65  70 74 69 6F 6E 20 68 61   the exce ption ha
      6E 64 6C 65 72 20 6C 61  6D 62 64 61            ndler la mbda

The client might be reporting this RST as a protocol error:
HTTP/2 stream error "INTERNAL_ERROR". A Reset is being sent to the stream.

The RST is caused because the exception hander re-threw the original exception after the your custom code. The reason this specifically affects a 404 is because of this check:

if (context.Response.StatusCode != StatusCodes.Status404NotFound || _options.AllowStatusCode404Response)

We found that too often the exception handler was pointed at a missing error page. You can override that by setting AllowStatusCode404Response.

There was an earlier HasStarted check to make sure we didn't try to render an error page in the middle of an existing page, but that check isn't repeated after running the exception handler.

Proposal: Check HasStarted before doing the 404 check. Do not rethrow if the exception handler started the response (unless it threw).

General guidance: Don't use exceptions as a normal means of producing status codes. The ExceptionHandler is only meant as a fallback for unexpected exceptions. MVC controllers should set the status code themselves for normal business logic scenarios.

@RyanClementsHax
Copy link
Author

Ah ok, so this is by design. Also thank you for your guidance. If I still need to handle a 404 in the exception handler, I see that I need to set that property to true. Looking through the code, I found this test which shows how to configure this, although this doesn't seem to work. I'm now getting 500s on the front end. Am I doing something wrong or is there a further issue here?

.Configure<ExceptionHandlerOptions>(options =>
{
    options.AllowStatusCode404Response = true;
    options.ExceptionHandler = async context =>
    {
        var errorFeature = context.Features.Get<IExceptionHandlerFeature>();
        var ex = errorFeature.Error;

        context.Response.StatusCode = ex switch
        {
            DomainException domainEx => StatusCodes.Status400BadRequest,
            NotFoundException notFoundEx => StatusCodes.Status404NotFound,
            _ => StatusCodes.Status500InternalServerError,
        };

        await context.Response.WriteAsync("There was an error caught in the exception handler lambda");
    };
});

@Tratcher Tratcher added the bug This issue describes a behavior which is not expected - a bug. label Mar 18, 2021
@Tratcher
Copy link
Member

You'll still need to call UseExceptionHandler() to include the middleware.

@RyanClementsHax
Copy link
Author

RyanClementsHax commented Mar 19, 2021

Thanks. That was the part that I was missing.

I updated the example repo with the resolution.

@BrennanConroy
Copy link
Member

Triage: Relevant comment about fixing the bug #31024 (comment)

@BrennanConroy BrennanConroy added the help wanted Up for grabs. We would accept a PR to help resolve this issue label Mar 19, 2021
@BrennanConroy BrennanConroy added this to the Backlog milestone Mar 19, 2021
@ghost
Copy link

ghost commented Mar 19, 2021

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

@ghost
Copy link

ghost commented Mar 19, 2021

Thanks for contacting us.
We're moving this issue to the Next sprint planning milestone for future evaluation / consideration. We will evaluate the request when we are planning the work for the next milestone. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

@BrennanConroy BrennanConroy removed the help wanted Up for grabs. We would accept a PR to help resolve this issue label Mar 19, 2021
@halter73
Copy link
Member

I have an alternative suggestion. Instead of rethrowing the original Exception, throw a custom suggestion suggesting configuring _options.AllowStatusCode404Response if you don't want an exception to be thrown/rethrown with the original exception as the InnerException.

@ghost ghost locked as resolved and limited conversation to collaborators Apr 30, 2021
@amcasey amcasey added area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions and removed area-runtime labels Aug 24, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions bug This issue describes a behavior which is not expected - a bug.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants