RequestSizeLimitAttribute: HTTP 500 instead of 413 in ASP.NET Core 2.1.401

Viewed 2164

I have [RequestSizeLimit] on my API controller, and it kinda works as expected: requests bigger than specified limit are rejected.

    [HttpPut]
    [RequestSizeLimit(120_000_000)]
    public async Task<IActionResult> Put(IFormCollection form)
    {
       ...
    }

The problem is, an exception is thrown:

Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Request body too large.
   at Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException.Throw(RequestRejectionReason reason)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1MessageBody.ForContentLength.OnReadStarting()
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.MessageBody.TryInit()
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.MessageBody.ReadAsync(Memory`1 buffer, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestStream.ReadAsyncInternal(Memory`1 buffer, CancellationToken cancellationToken)

So HTTP 500 is returned, but I would expect 413 or 400. And I don't expect an exception, since this is a perfectly normal situation.

Could not find any documentation on this. What is the right way to return 413 for requests that are too big?

3 Answers

Kestrel responds with a 413 Payload Too Large response, but HttpSys responds with a generic 500 Internal Server Error response. I assume you use the second one. In this case you can implement Exception handling middelware to handle this case:

public class ExceptionMiddleware
{
    private readonly RequestDelegate _next;

    public ExceptionMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        try
        {
            await _next(httpContext);
        }
        catch (Exception ex)
        {
            HandleExceptionAsync(httpContext, ex);
        }
    }

    private static void HandleExceptionAsync(HttpContext context, Exception exception)
    {
        if (exception is BadHttpRequestException badRequestException && badRequestException.Message == "Request body too large.")
        {
            context.Response.StatusCode = (int) HttpStatusCode.RequestEntityTooLarge;
        }
    }
}

And register it in Configure in Startup.cs:

public void Configure(IApplicationBuilder app)
{
    ...
    app.UseMiddleware<ExceptionMiddleware>();
    ...
}

As alternative you can also use Exception filter

I ran into this same behavior while running in debug in Visual Studio. I discovered that the DeveloperExceptionPageMiddleware causes it. I noticed fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware in the server logs above the BadHttpRequestException exception.

Removing this from my Startup.cs gives 413 responses as expected.

if (env.IsDevelopment())
    app.UseDeveloperExceptionPage();

Now the server logs show fail: Microsoft.AspNetCore.Server.Kestrel instead.

Building upon Alex Riabov's answer, it is now possible to catch the actual internal error code thrown without parsing the text.

So the updated part of the exception handler is:

private static void HandleExceptionAsync(HttpContext context, Exception exception)
{
    if (exception is BadHttpRequestException badRequestException && badRequestException.StatusCode == StatusCodes.Status413PayloadTooLarge)
    {
        context.Response.StatusCode = badRequestException.StatusCode;
    }
}
Related