dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.46k stars 10.03k forks source link

Kestrel Return 504 while frequently encountered with the "request body too large" problem #23840

Open stg609 opened 4 years ago

stg609 commented 4 years ago

Describe the bug

Use the RequestSizeLimitAttribute together with IFormFile on an WebApi action method. The expected behaviour when the request body is too large is to return a 500 internal server error and tell me the request body is too large. However, if the client frequently (e.g. 1 request each second) send files larger than the limit, the kestrel will probably return 504 with 0 byte in response.

To Reproduce

It's very easy to reproduce.

  1. Simply create a default asp.net core 3.1 webapi project,
  2. Add a new action in any controller like below:
    [HttpPost]
    [RequestSizeLimit(4194304)]
    public ActionResult Post(IFormFile video)
    {
        return Ok();
    }
  3. Start the api using kestrel as the host (release mode and without debugger attached (Ctrl + F5)).
  4. Upload any file larger than 4M using postman or fiddler, you will probably find the 504 response in 10 times.

I also upload the code here: https://github.com/stg609/Issue23840

Exceptions (if any)

Postman

I get below snapshot using Postman. image image

Fiddler

I get below snapshot using Fiddler to send the request, as you can see, there're multiple 504 response. image

And the raw response :

HTTP/1.1 504 Fiddler - Receive Failure Date: Fri, 10 Jul 2020 05:19:35 GMT Content-Type: text/html; charset=UTF-8 Connection: close Cache-Control: no-cache, must-revalidate Timestamp: 13:19:35.564

[Fiddler] ReadResponse() failed: The server did not return a complete response for this request. Server returned 0 bytes.

Further technical details

.NET Core SDK(反映任何 global.json): Version: 3.1.300 Commit: b2475c1295

运行时环境: OS Name: Windows OS Version: 10.0.17134 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\3.1.300\

Host (useful for support): Version: 3.1.4 Commit: 0c2e69caa6

.NET Core SDKs installed: 2.1.502 [C:\Program Files\dotnet\sdk] 2.1.507 [C:\Program Files\dotnet\sdk] 2.2.105 [C:\Program Files\dotnet\sdk] 3.1.300 [C:\Program Files\dotnet\sdk]

.NET Core runtimes installed: Microsoft.AspNetCore.All 2.1.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.2.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.2.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.App 2.1.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.2.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.2.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.1.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 2.1.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.2.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.2.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.1.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 3.1.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

davidfowl commented 4 years ago

Do you get the 504 without fiddler running?

Tratcher commented 4 years ago

Those logs are showing the expected behavior.

  1. Reading the request body fails because of the size limit.
  2. A 500 response is sent.
  3. Kestrel (ConsumeAsync) needs to drain the existing request off the wire in order to process the next request. It's too big, so the only thing it can do is close the connection.
  4. Fiddler sees the connection close and reports it as a 504.

2 and 3 do lead to a race condition where the client may or may not receive and process the 500 response before the connection gets closed. There's not much kestrel can do about this, it depends both on the timing, and on how the client processes responses.

stg609 commented 4 years ago

@davidfowl No, most of the time the client will throw an exception said the connection was broken. I only find the 504 with fiddler.

stg609 commented 4 years ago

@Tratcher Thanks for the explanation. Now I know the reason why the connection is closed. However, I still think it's not a good behavior.

  1. The kestrel should produce a consistent response. And document it somewhere to help developer understand the reason.
  2. As a client, if a user upload a file larger than the limit, I expect the client to receive a consistent 4xx status code, as 4xx indicates it's a user's mistake,(e.g. 413 payload too large).

And as a Api developer, I'm expecting there's some workarounds to help me return consistent response to my client.

Tratcher commented 4 years ago

We'll have to investigate the response flushing in Kestrel to see if we can ensure it goes out before the drain error happens.

You might also want to try this using HTTP/2. The mechanics are quite different and I think it would work there.