ASP.NET CORE“BadHttpRequestException:请求内容意外结束。” 导致未来的连接卡住

snu*_*san 6 multipartform-data asp.net-core asp.net-core-webapi asp.net-core-6.0

我正在构建 ASP.NET Core 6.0 Web API。该 API 具有接收multipart/form-data请求并将各部分保存到文件中的端点。如果在处理请求期间互联网连接被切断,则以下错误将记录到应用程序的控制台中:

Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Unexpected end of request content. at Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException.Throw(RequestRejectionReason reason) at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1ContentLengthMessageBody.ReadAsyncInternal(CancellationToken cancellationToken) at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestStream.ReadAsyncInternal(Memory 1 buffer, CancellationToken cancellationToken) at Microsoft.AspNetCore.WebUtilities.BufferedReadStream.EnsureBufferedAsync(Int32 minCount, CancellationToken cancellationToken) at Microsoft.AspNetCore.WebUtilities.MultipartReaderStream.ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken) at System.IO.Stream.CopyToAsyncInternal(Stream destination, Int32 bufferSize, CancellationToken cancellationToken) at AppName.Utilities.FileHelpers.ProcessStreamedFile(MultipartSection section, ContentDispositionHeaderValue contentDisposition, IConfiguration conf, ModelStateDictionary modelState, CancellationToken ct) in C:\AppName\Utilities\FileHelpers.cs:line 153

连接恢复后,应用程序不会处理来自用于发送失败请求的同一台计算机的新请求,除非重新启动应用程序。所有 API 端点都会发生这种情况,而不仅仅是失败的端点。来自本地主机的邮递员请求按其应有的方式进行。

我的问题是:是什么导致 API 陷入这种困境?我不明白连接丢失为何以及如何导致应用程序停止接收来自远程计算机的新请求。

这是我用来处理多部分的代码,在控制器中调用此函数来处理多部分 POST 请求。它遍历多个部分并调用ProcessStreamedFile每个部分。它还有其他功能,我无法在这里分享,但与 IO 或 HTTP 通信无关。

[RequestFormLimits(ValueLengthLimit = int.MaxValue, MultipartBodyLengthLimit = int.MaxValue)]
private async Task<ActionResult> ReadAndSaveMultipartContent()
{
    try
    {
        var boundary = Utilities.MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType),MaxMultipartBoundaryCharLength);

        var cancellationToken = this.HttpContext.RequestAborted;
        var reader = new MultipartReader(boundary, HttpContext.Request.Body);
        var section = await reader.ReadNextSectionAsync(cancellationToken);

        while (section != null)
        {
            try
            {
                var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);

                if (hasContentDispositionHeader)
                {
                    // This check assumes that there's a file
                    // present without form data. If form data
                    // is present, this method immediately fails
                    // and returns the model error.
                    if (!Utilities.MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
                    {
                        ModelState.AddModelError("File", $"The request couldn't be processed (Error 2).");
                        return BadRequest(ModelState);
                    }
                    else
                    {
                        var streamedFilePath = await FileHelpers.ProcessStreamedFile(
                                section, contentDisposition, Startup.Configuration, ModelState,
                                cancellationToken);

                        if (streamedFilePath == "-1")
                        {
                            return BadRequest();
                        }
                            
                        /* MORE CODE HERE */

                            
                }
                else
                {
                    // We go here if contentDisposition header is missing.
                    return BadRequest();
                }
            }
            catch (Exception ex)
            {
                return BadRequest();
            }
            // Drain any remaining section body that hasn't been consumed and
            // read the headers for the next section.
            section = await reader.ReadNextSectionAsync(cancellationToken);
        }
    } catch (Exception ex)
    {
        return BadRequest("Error in reading multipart request. Multipart section malformed or headers missing. See log file for more details.");
    }
    return Ok();
}
Run Code Online (Sandbox Code Playgroud)

请忽略上面代码中的嵌套 try-catch,这是有原因的,我不得不从显示的代码中省略它。下面是 的代码ProcessStreamedFile

public static async Task<string> ProcessStreamedFile(MultipartSection section, Microsoft.Net.Http.Headers.ContentDispositionHeaderValue contentDisposition,IConfiguration conf, ModelStateDictionary modelState, CancellationToken ct)
{
    var completeFilepath = GetFilepath(section, contentDisposition, conf);
    var dirPath = Path.GetDirectoryName(completeFilepath);Directory.CreateDirectory(dirPath);
    try
    {
        using var memoryStream = new FileStream(completeFilepath, FileMode.Create);
        await section.Body.CopyToAsync(memoryStream, ct);

        // Check if the file is empty or exceeds the size limit.
        if (memoryStream.Length == 0)
        {
            modelState.AddModelError("File", "The file is empty.");
            memoryStream.Close();
        }
        else
        {
            memoryStream.Close();
            return completeFilepath;
        }
    }
    catch (Exception ex)
    {
        return "-1";
    }
    return completeFilepath;
}
Run Code Online (Sandbox Code Playgroud)

C:\AppName\Utilities\FileHelpers.cs:line 153错误 ( )中引用的行是await section.Body.CopyToAsync(memoryStream, ct);

我尝试添加 CancellationToken 希望它能够正确处理请求的剪切,手动关闭HttpContextwithHttpContext.Abort()HttpContext.Session.Clear()。这些都没有以任何方式改变行为。

snu*_*san 4

解决方案

此问题是由我用于建立连接的端口转发引起的。由于我们的网络配置,我最初必须使用 Putty 隧道并将远程计算机(发送请求的计算机)端口转发到我的本地计算机(运行服务器)。不知何故,当连接丢失时,这条隧道就会被卡住。现在我能够更改我们的网络,以便我可以使用实际的公共 IP 将请求直接发送到我的本地计算机,并且一切正常。

我不知道为什么腻子隧道被卡住了,但到目前为止我能够避免这个问题,并且由于时间限制而无法深入挖掘。