分段表单文件上传 - 上传的文件有时在文件末尾有额外的换行符

Mar*_*cus 8 c# multipartform-data

我有一个非常疯狂的问题,我希望有人能给我一些如何解决它的建议。

在客户端,我使用 C# 将多部分表单上传到服务器。表单中有一个 MD5 哈希字段和一个音频文件字段。

问题是,有时上传的音频文件在文件末尾有换行符,而客户端的原始文件中不存在换行符。这当然会导致 MD5 哈希检查失败。

我很惊讶这种情况只是偶尔发生。在 200 个请求中,有一个请求存在所描述的问题。

这是我的 HttpMultipartFormSender 类的代码:

public class HttpMultipartFormSender
{
    private HttpRequestMessage _requestMessage;

    public HttpMultipartFormSender(string requestUri)
    {
        UploadProgressHandler = new ProgressMessageHandler();
        var boundary = "----------" + DateTime.Now.Ticks.ToString("x", CultureInfo.InvariantCulture);
        Content = new MultipartFormDataContent(boundary);
        _requestMessage = new HttpRequestMessage(HttpMethod.Post, requestUri);
    }

    public MultipartFormDataContent Content { get; set; }

    public ProgressMessageHandler UploadProgressHandler { get; }

    public async Task<bool> SendFormAsync()
    {
        _requestMessage.Content = Content;
        var client = HttpClientFactory.Create(UploadProgressHandler);
        client.Timeout = TimeSpan.FromMinutes(30);

        var httpResponse = await client.SendAsync(_requestMessage);
        if (!httpResponse.IsSuccessStatusCode)
        {
            throw new InvalidOperationException("Exception thrown while file upload");
        }

        return true;
    }

    #endregion
}
Run Code Online (Sandbox Code Playgroud)

用法如下:

VmPropUserMessage = $"File: {Path.GetFileName(filePath)} is uploading...";
await using var fileStream =
    new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);

var md5StringContent = new StringContent(GetFileMd5(filePath));
var fileStreamContent = new StreamContent(fileStream);
fileStreamContent.Headers.Add("Content-Type", "application/octet-stream");

var formSender       = new HttpMultipartFormSender($"{VmPropSelectedTranscoder.Url}/upload");
formSender.UploadProgressHandler.HttpSendProgress += FileUploadProgressChanged;
formSender.Content.Add(md5StringContent, "file_hash");
formSender.Content.Add(fileStreamContent, "file", filePath);
await formSender.SendFormAsync();
Run Code Online (Sandbox Code Playgroud)

在服务器端,我使用这段代码来存储请求中的流:

private static void SaveFile(TranscodingJobModel transcodingJob, Stream requestFileContent)
{
    var uploadedFilePath = $"{AppConfig.AudioUploadDir}\\{transcodingJob.GetTranscodingJobTmpFileName}";

    using (var fileSaveStream = File.Create(uploadedFilePath))
    {
        requestFileContent.CopyTo(fileSaveStream);
        fileSaveStream.Flush(true);
        fileSaveStream.Close();
        requestFileContent.Close();
    }
}
Run Code Online (Sandbox Code Playgroud)

有时我有这样的结果:

在客户端,文件仅以一个新行结尾:

在源端

在服务器端文件以两行新行结尾:

目的地一侧

有人知道为什么会发生这种情况吗?

提前致谢

Ada*_*dam 0

多部分内容规范对 CRLF/换行有一些特定的语法期望:

https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html

请注意,封装边界必须出现在行的开头,即在 CRLF 之后,并且初始 CRLF 被视为封装边界的一部分,而不是前面部分的一部分。边界后面必须紧跟另一个 CRLF 和下一部分的标头字段,或者是两个 CRLF,在这种情况下,下一部分没有标头字段(因此假定为 Content-Type text/清楚的)。注意:封装线之前的 CRLF 被视为边界的一部分,因此可能存在不以 CRLF(换行符)结尾的部分。因此,必须考虑以换行符结尾的主体部分应在封装线之前有两个 CRLF,第一个是前一个主体部分的一部分,第二个是封装边界的一部分。

封装边界以 CRLF 开头的要求意味着多部分实体的主体本身必须在第一个封装行之前以 CRLF 开头——也就是说,如果不使用“前导码”区域,则必须遵循实体标头由两个 CRLF 组成。这确实是此类实体应该组成的方式。然而,宽容的邮件阅读程序可能会将以不是由 CRLF 启动的封装行开头的 multipart 类型的主体解释为封装边界,但兼容的邮件发送程序不得生成此类实体。

MultipartFormDataContent类型继承了 MultipartContent行为,其中底层源有助于说明 .NET 在组合多个底层内容元素时如何处理应用此规范:

我提出这一点是因为规范的一部分涉及添加新行作为内容分段的一部分,这可能是您使用有问题的文件在客户端上编写单元测试以生成 MultipartFormDataContent 并在多个阶段对其进行评估的机会缩小问题所在的范围以及问题是否出在客户端的内容生成代码或服务器的消费代码上。如果您在将多部分内容处理成流之前检查文件内容,它是否仍然匹配?如果您将多部分内容处理到流中并在本地读取该流(不通过网络发送),它仍然匹配吗?