C# StreamContent 和 File.OpenRead() 不产生 HTTP'able 多部分内容

Posted

技术标签:

【中文标题】C# StreamContent 和 File.OpenRead() 不产生 HTTP\'able 多部分内容【英文标题】:C# StreamContent and File.OpenRead() Not Producing HTTP'able multipart contentC# StreamContent 和 File.OpenRead() 不产生 HTTP'able 多部分内容 【发布时间】:2019-02-26 01:19:10 【问题描述】:

我有一个 .Net Core 2.0 应用程序,它使用多部分内容将文件发送到 Web API 端点。我看过的所有地方,例如C# HttpClient 4.5 multipart/form-data upload,都让它看起来应该像将 FileStream 传递给 StreamContent 一样简单。但是,当我发帖时,看起来文件是作为文本附加的,而不是位。

实际代码:

var request = new HttpRequestMessage()

    Method = HttpMethod.Post,
    RequestUri = new Uri( "http://localhost:10442/filetest" )
;
var multiContent = new MultipartFormDataContent();

var filestream = File.OpenRead( path );
var filename = Path.GetFileName( path );
var streamContent = new StreamContent( filestream );
streamContent.Headers.Add( "Content-Type", "application/octet-stream" );
streamContent.Headers.Add( "Content-Disposition", $"form-data; name=\"file1\"; filename=\"filename\"" );

multiContent.Add( streamContent, "file", filename );
request.Content = multiContent;

var response = await new HttpClient().SendAsync( request );

请求看起来像这样,正如您可能注意到的那样,它并不都在一行中(我认为这是一个/THE 问题):

POST http://localhost:10442/filetest HTTP/1.1
Content-Type: multipart/form-data; boundary="c5295887-425d-4ec7-8638-20c6254f9e4b"
Content-Length: 88699
Host: localhost:10442

--c5295887-425d-4ec7-8638-20c6254f9e4b
Content-Type: application/octet-stream
Content-Disposition: form-data; name="file1"; filename="somepdf.pdf"

%PDF-1.7
%       
1 0 obj
<</Type/Catalog/Version/1.7/Pages 3 0 R/Outlines 2 0 R/Names 8 0 R/Metadata 31 0 R>>
endobj

Fiddler 将整个帖子一直显示到末尾边界,但端点中的 await Request.Content.ReadAsStringAsync() 仅显示前几十个字节(看起来好像流还没有完成,但如果 Fiddler 得到了所有内容,我的端点不应该也有吗?)。

我在尝试访问远程端点时遇到了类似的问题;我构建了这个端点来进行本地测试。

我得到的例外是:“MIME 多部分流意外结束。MIME 多部分消息不完整。”对我来说,如果我真的只获得我的流的一部分,或者换行符正在抛出一些东西,这都是有意义的。

我也尝试将一些 Idisposables 放入 Usings,但正如预期的那样,这会关闭流并且我会以这种方式得到异常。

为了完整起见,这里是我调用的端点:

public async void ReceiveFiles()

    // exception happens here:
    var mpData = await Request.Content.ReadAsMultipartAsync();
    await Task.FromResult( 0 );

【问题讨论】:

为什么要使用表单正文而不是分块传输编码,或者只是设置一个 Content-Length 标头并将文件字节放在一个请求正文中? 在答案***.com/a/59308653/5333683中将参数作为multipear dotnet 3.0 发送示例 【参考方案1】:

试试这样的:

static int Main(string[] args)

    var request = new HttpRequestMessage()
    
        Method = HttpMethod.Post,
        RequestUri = new Uri("http://localhost:10442/filetest")
    ;
    var path = "c:\\temp\\foo.bak";

    using (var filestream = File.OpenRead(path))
    
        var length = filestream.Length.ToString();
        var streamContent = new StreamContent(filestream);
        streamContent.Headers.Add("Content-Type", "application/octet-stream");
        streamContent.Headers.Add("Content-Length", length);


        request.Content = streamContent;

        Console.WriteLine($"Sending length bytes");
        var response = new HttpClient().SendAsync(request).Result;


        Console.WriteLine(response.Content.ReadAsStringAsync().Result);
    

    Console.WriteLine("Hit any key to exit");
    Console.ReadKey();
    return 0;

    [HttpPost]
    public async Task<IActionResult> Upload()
    
        var buf = new byte[1024 * 64];
        long totalBytes = 0;
        using (var rs = Request.Body)
        
            while (1 == 1)
            
                int bytesRead = await rs.ReadAsync(buf, 0, buf.Length);
                if (bytesRead == 0) break;
                totalBytes += bytesRead;
            
        
        var uploadedData = new
        
            BytesRead = totalBytes
        ;
        return new JsonResult(uploadedData) ;
    

【讨论】:

【参考方案2】:

我正在尝试解决一个类似的问题,但我还不是 100% 的解决方案,但也许我的一些研究可以帮助你。

阅读 .NET 核心文件上传的 microsoft 文档对我很有帮助,特别是对于使用流和多部分表单数据的大文件: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-2.1#uploading-large-files-with-streaming

您已经引用了它,但此答案中有一些相关的有用信息: C# HttpClient 4.5 multipart/form-data upload

这解释了 content-disposition 标头的详细信息以及它如何与多部分表单数据请求一起使用:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#As_a_header_for_a_multipart_body

至于您将文件作为文本而不是位发送的具体问题,因为 http 是基于文本的,它只能作为文本发送,但该文本可以按照您认为合适的方式进行编码。也许您的 StreamContent 需要使用特定的编码,例如 base64 编码或类似的?我确实相信换行符在多部分请求中很重要,因此希望根据需要设置文件内容的编码就足够了。

另一种可能性:您是否需要在文件部分的标题或 StreamContent 的定义中设置附加信息以指示它应该继续,或者边界信息没有正确输入?见Multipart forms from C# client

【讨论】:

【参考方案3】:

我使用这个库:https://github.com/jgiacomini/Tiny.RestClient 发送多部分文件更容易发送多部分文件。

这里有一个示例: 等待 client.PostRequest("MultiPart/Test")。 AsMultiPartFromDataRequest()。 AddStream(stream1, "request", "request2.bin")。 AddStream(stream2, "request", "request2.bin") 执行异步();

【讨论】:

以上是关于C# StreamContent 和 File.OpenRead() 不产生 HTTP'able 多部分内容的主要内容,如果未能解决你的问题,请参考以下文章

POST StreamContent 与多个文件

HttpClient StreamContent 附加文件名两次

C# Azure BlobClient 覆盖时在上传时设置 AccessTier

HTTP 客户端未向代理服务器 C# 进行身份验证

error while loading shared libraries: libg2o_core.so: cannot open shared object file: No such file o

链接器可以内联函数吗?