【问题标题】:ASP.NET - Cannot get error response immediately when uploading very large file?ASP.NET - 上传非常大的文件时无法立即获得错误响应?
【发布时间】:2019-11-30 12:33:03
【问题描述】:

当我使用 Postman 尝试将大文件上传到我的服务器(使用 .NET Core 2.2 编写)时,Postman 立即显示 HTTP Error 404.13 - Not Found 错误:The request filtering module is configured to deny a request that exceeds the request content length

但是当我使用我的代码上传那个大文件时,它会卡在发送文件的线路上。

我的客户代码:

    public async void TestUpload() {
        StreamContent streamContent = new StreamContent(File.OpenRead("D:/Desktop/large.zip"));
        streamContent.Headers.Add("Content-Disposition", "form-data; name=\"file\"; filename=\"large.zip\"");
        MultipartFormDataContent multipartFormDataContent = new MultipartFormDataContent();
        multipartFormDataContent.Add(streamContent);
        HttpClient httpClient = new HttpClient();
        Uri uri = new Uri("https://localhost:44334/api/user/testupload");
        try {
            HttpResponseMessage httpResponseMessage = await httpClient.PostAsync(uri, multipartFormDataContent);
            bool success = httpResponseMessage.IsSuccessStatusCode;
        }
        catch (Exception ex) {

        }
    }

我的服务器代码:

    [HttpPost, Route("testupload")]
    public async Task UploadFile(IFormFileCollection formFileCollection) {
        IFormFileCollection formFiles = Request.Form.Files;
        foreach (var item in formFiles) {
            using (var stream = new FileStream(Path.Combine("D:/Desktop/a", item.FileName), FileMode.Create)) {
                await item.CopyToAsync(stream);
            }
        }
    }

我的客户端代码卡在HttpResponseMessage httpResponseMessage = await httpClient.PostAsync(uri, multipartFormDataContent) 行,而服务器没有收到任何请求(我使用断点来确保这一点)。

文件越大卡住的时间越长。查看任务管理器,我可以看到我的客户端程序使用了高 CPU 和磁盘,因为它实际上正在将文件上传到服务器。过了一会儿,代码移动到下一行,即

bool success = httpResponseMessage.IsSuccessStatusCode

然后通过阅读响应内容,我得到了与 Postman 完全相同的结果。

现在我想知道如何立即得到错误以便能够及时通知用户,我不想等待很长时间。

请注意,当我使用 Postman 上传大文件时,我的服务器也没有收到任何请求。我想我遗漏了一些东西,也许我的客户端代码有问题。

编辑:实际上我认为这是客户端错误。但如果是服务器端错误,那对我来说仍然没有太大意义。因为,让我理清思路。我想创建一个可以跨项目使用的小助手类,也许我也可以与我的朋友分享。所以我认为它应该能够像 Postman 一样,尽快确定错误。如果 Postman 可以,我也可以。

EDIT2:奇怪的是,今天我发现 Postman 事先不知道服务器是否接受大请求,我上传了一个大文件,我看到它实际上将整个文件发送到服务器,直到它得到响应。现在我不再相信自己了,为什么我认为邮递员会提前知道错误,我一定是愚蠢的。但这确实意味着我找到了一种比 Postman 做得更好的方法,所以我认为这个问题可能对某人有用。

【问题讨论】:

  • 我怀疑邮递员正在使用 http 1.1,这是块模式,而您的 c# 正在使用 http 1.0,这是流模式。我会使用像wireshark或fiddler这样的嗅探器,并将邮递员请求与c#请求进行比较以查看差异。
  • 您的服务器将接收您从 Postman 发送的请求,它只是没有到达您的代码,因为它在管道的早期被拒绝了。您需要更改一些配置以允许大请求。
  • @dezfowler 我知道如何允许大请求。但我的意思是我希望我的代码像 Postman 一样运行,我不会对我的服务器进行任何更改,因为这不是原因。
  • 不正确。您的服务器原因。客户端发送请求,然后等待服务器响应。在某些时候,请求最终会超时,但在此之前,服务器会 100% 中止并发送错误响应。否则,客户端只会等到超时阈值。
  • @peanut 我并不是说你的服务器是这个问题的原因,只是如果你想看到请求通过你需要在服务器端更改一些东西。我认为这可能是因为您没有在流内容上设置 Content-Length 标头。您应该从文件系统中获取文件长度并设置标题。否则 StreamContent 可能正在读取整个文件,以便首先确定长度。它不知道流的来源,并且能够以不同的方式获得长度。

标签: c# asp.net-core postman


【解决方案1】:

现在我可以做我想做的事了。但首先我要感谢@Marko Papic,你的信息确实帮助我思考如何做我想做的事。

我正在做的是:

  1. 首先,创建一个空的ByteArrayContent 请求,其中包含我要上传到服务器的文件的ContentLength
  2. 其次,将HttpResponseMessage = await HttpClient.SendAsync(HttpRequestMessage) 包围在try-catch 块中。 catch 块捕获 HttpRequestException,因为我正在发送一个包含文件长度的请求,但我的实际内容长度为 0,所以它会抛出一个带有消息 Cannot close stream until all bytes are writtenHttpRequestException
  3. 如果代码到达catch 块,则表示服务器允许请求文件大小或更大。如果没有异常并且代码移到下一行,那么如果HttpResponseMessage.StatusCode404,这意味着服务器DENIES 请求大于文件大小。 HttpResponseMessage.StatusCodeNOT 404 的情况永远不会发生(不过我不确定这个)。

到目前为止我的最终代码:

    private async Task<bool> IsBigRequestAllowed() {
        FileStream fileStream = File.Open("D:/Desktop/big.zip", FileMode.Open, FileAccess.Read, FileShare.Read);
        if(fileStream.Length == 0) {
            fileStream.Close();
            return true;
        }
        HttpRequestMessage = new HttpRequestMessage();
        HttpMethod = HttpMethod.Post;
        HttpRequestMessage.Method = HttpMethod;
        HttpRequestMessage.RequestUri = new Uri("https://localhost:55555/api/user/testupload");
        HttpRequestMessage.Content = new ByteArrayContent(new byte[] { });
        HttpRequestMessage.Content.Headers.ContentLength = fileStream.Length;
        fileStream.Close();
        try {
            HttpResponseMessage = await HttpClient.SendAsync(HttpRequestMessage);
            if (HttpResponseMessage.StatusCode == HttpStatusCode.NotFound) {
                return false;
            }
            return true; // The code will never reach this line though
        }
        catch(HttpRequestException) {
            return true;
        }
    }

注意:请注意,我的方法仍然存在问题。我的代码的问题是ContentLength 属性,它不应该是文件的长度,它应该更大。例如,如果我的文件长度正好是 1000 字节,那么如果文件成功上传到服务器,那么服务器获取到的Request 具有更大的ContentLength 值。因为HttpClient不只是发送文件的内容,而且还要发送很多信息。它必须发送边界、内容类型、连字符、换行符等...一般来说,您应该事先找出HttpClient 将与您的文件一起发送的确切字节,以使这种方法完美运行(我还是不知道有多远,我的时间不多了。我会找出并更新我的答案)。

现在我可以提前确定服务器是否可以接受与我的用户想要上传的文件一样大的请求。

【讨论】:

    【解决方案2】:

    您的问题与您的服务器端 C# 代码无关。由于客户端和服务器之间发生的事情,您的请求被卡住了(“服务器”是指 IIS、Apache、Nginx...,而不是您的服务器端代码)。

    在 HTTP 中,大多数客户端在发送所有请求数据之前不会读取响应。因此,即使您的服务器发现请求过大并返回错误响应,客户端也会在服务器接受整个请求之前不要读取该响应。

    服务器端可以查看this question,但我认为客户端处理会更方便,发送前先检查文件大小服务器(这基本上是 Postman 在你的情况下所做的)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-07-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-18
      • 2020-11-27
      • 2019-04-01
      • 1970-01-01
      相关资源
      最近更新 更多