Skip to content

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

Closed
@RyanClementsHax

Description

@RyanClementsHax

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

Metadata

Metadata

Assignees

Labels

area-networkingIncludes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractionsbugThis issue describes a behavior which is not expected - a bug.

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions