使用 Flurl 发布 `multipart/form-data`

Posting `multipart/form-data` with Flurl

我需要post以下请求:

POST http://target-host.com/some/endpoint HTTP/1.1
Content-Type: multipart/form-data; boundary="2e3956ac-de47-4cad-90df-05199a7c1f53"
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Content-Length: 6971
Host: target-host.com

--2e3956ac-de47-4cad-90df-05199a7c1f53
Content-Disposition: form-data; name="some-label"

value
--2e3956ac-de47-4cad-90df-05199a7c1f53
Content-Disposition: form-data; name="file"; filename="my-filename.txt"

<file contents>
--2e3956ac-de47-4cad-90df-05199a7c1f53--

我可以使用 Python requests 库非常轻松地做到这一点,如下所示:

import requests

with open("some_file", "rb") as f:
    byte_string = f.read()

requests.post(
    "http://target-host.com/some/endpoint",
    data={"some-label": "value"},
    files={"file": ("my-filename.txt", byte_string)})

有什么方法可以对 Flurl.Http 库做同样的事情吗?

我的 documented 方法的问题是它会为每个 key-value 对插入 Content-Type header 并且它会插入 filename*=utf-8'' header 为文件数据。但是,我尝试向 post 发送请求的服务器不支持此功能。另请注意 header 中 namefilename 值周围的双引号。

编辑:下面是我用 Flurl.Http:

发出 post 请求的代码
using System.IO;
using Flurl;
using Flurl.Http;

namespace ConsoleApplication
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var fs = File.OpenRead("some_file");

            var response = "http://target-host.com"
                .AppendPathSegment("some/endpoint")
                .PostMultipartAsync(mp => mp
                    .AddString("some-label", "value")
                    .AddFile("file", fs, "my-filename.txt")
                ).Result;
        }
    }
}

根据 spec(日期为 2011 年 6 月),建议同时发送 filenamefilename* 以获得最大兼容性:

Many user agent implementations predating this specification do not understand the "filename*" parameter. Therefore, when both "filename" and "filename*" are present in a single header field value, recipients SHOULD pick "filename*" and ignore "filename". This way, senders can avoid special-casing specific user agents by sending both the more expressive "filename*" parameter, and the "filename" parameter as fallback for legacy recipients.

如果 filename* 确实导致了对 的调用失败 ,那么遵守 HTTP 规范的服务器确实存在问题。此外,将 namefilename 括在引号中非常 non-standard.

也就是说,Flurl 的快捷方式涵盖了 90% 的情况,但您始终可以使用底层 HttpClient API 来涵盖像这种情况这样的异常情况。在这种情况下,我认为您需要手动构建内容,以便处理那些 Content-Disposition headers:

var mpc = new MultipartContent();
var sc = new StringContent("value");
sc.Headers.Add("Content-Disposition", "form-data; name=\"some-label\"");
mpc.Add(sc);
var fc = new StreamContent(fs);
fc.Headers.Add("Content-Disposition", "form-data; name=\"file\"; filename=\"my-filename.txt\"");
mpc.Add(fc);

然后您可以像这样将它与 Flurl 一起使用:

var response = await "http://target-host.com"....PostAsync(mpc);