为什么有人会使用multipart / form-data进行混合数据和文件传输?


14

我正在使用C#,正在编写的2个应用程序之间进行一些通信。我开始喜欢Web API和JSON。现在,我正在编写例程以在两个服务器之间发送包含一些文本数据和文件的记录。

根据互联网,我应该使用multipart / form-data请求,如下所示:

SO问题“来自C#客户端的多部分表单”

基本上,您手动编写一个遵循如下格式的请求:

Content-type: multipart/form-data, boundary=AaB03x

--AaB03x
content-disposition: form-data; name="field1"

Joe Blow
--AaB03x
content-disposition: form-data; name="pics"; filename="file1.txt"
Content-Type: text/plain

 ... contents of file1.txt ...
--AaB03x--

RFC 1867复制-HTML中基于表单的文件上传

这种格式对于习惯于使用JSON数据的人来说非常令人不快。因此,很明显,解决方案是创建一个JSON请求,并对文件进行Base64编码,并最终得到如下请求:

{
    "field1":"Joe Blow",
    "fileImage":"JVBERi0xLjUKJe..."
}

我们可以在任何需要的地方使用JSON序列化和反序列化。最重要的是,发送此数据的代码非常简单。您只需为JSON序列化创建类,然后设置属性即可。文件字符串属性的设置很简单:

using (FileStream fs = File.Open(file_path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    byte[] file_bytes = new byte[fs.Length];
    fs.Read(file_bytes, 0, file_bytes.Length);
    MyJsonObj.fileImage = Convert.ToBase64String(file_bytes);
}

每个项目都不再有愚蠢的分隔符和标头。现在剩下的问题是性能。因此,我对此进行了介绍。我有一组50个样本文件,需要通过电线发送,范围从50KB到1.5MB左右。首先,我写了几行代码,简单地将文件流式传输到字节数组,以将其与文件流式传输的逻辑进行比较,然后将其转换为Base64流。以下是我介绍的2个代码块:

直接流分析多部分/表单数据

var timer = new Stopwatch();
timer.Start();
using (FileStream fs = File.Open(file_path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    byte[] test_data = new byte[fs.Length];
    fs.Read(test_data, 0, test_data.Length);
}
timer.Stop();
long test = timer.ElapsedMilliseconds;
//Write time elapsed and file size to CSV file

流和编码以配置文件创建JSON请求

var timer = new Stopwatch();
timer.Start();
using (FileStream fs = File.Open(file_path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    byte[] file_bytes = new byte[fs.Length];
    fs.Read(file_bytes, 0, file_bytes.Length);
    ret_file = Convert.ToBase64String(file_bytes);
}
timer.Stop();
long test = timer.ElapsedMilliseconds;
//Write time elapsed, file size, and length of UTF8 encoded ret_file string to CSV file

结果是,简单读取始终花费0ms,而Base64编码最多花费5ms。以下是最长的时间:

File Size  |  Output Stream Size  |  Time
1352KB        1802KB                 5ms
1031KB        1374KB                 7ms
463KB         617KB                  1ms

但是,在生产中,如果没有先检查定界符就不会盲目地编写multipart / form-data吗?因此,我修改了表单数据代码,以便检查文件本身中的定界符字节,以确保一切正常。我没有编写优化的扫描算法,所以我只是将定界符缩小了,这样就不会浪费很多时间。

var timer = new Stopwatch();
timer.Start();
using (FileStream fs = File.Open(file_path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    byte[] test_data = new byte[fs.Length];
    fs.Read(test_data, 0, test_data.Length);
    string delim = "--DXX";
    byte[] delim_checker = Encoding.UTF8.GetBytes(delim);

    for (int i = 0; i <= test_data.Length - delim_checker.Length; i++)
    {
        bool match = true;
        for (int j = i; j < i + delim_checker.Length; j++)
        {
            if (test_data[j] != delim_checker[j - i])
            {
                match = false;
                break;
            }
        }
        if (match)
        {
            break;
        }
    }
}
timer.Stop();
long test = timer.ElapsedMilliseconds;

现在的结果向我显示,表单数据方法实际上将大大降低速度。以下是两种方法中时间> 0ms的结果:

File Size | FormData Time | Json/Base64 Time
181Kb       1ms             0ms
1352Kb      13ms            4ms
463Kb       4ms             5ms
133Kb       1ms             0ms
133Kb       1ms             0ms
129Kb       1ms             0ms
284Kb       2ms             1ms
1031Kb      9ms             3ms

看来我的定界符只有5个字符长,优化的算法似乎也不会做得更好。无论如何都不是3倍更好,这是执行Base64编码而不是检查文件字节是否有定界符的性能优势。

显然,Base64编码将使大小膨胀,如我在第一张表中所示,但是即使使用支持Unicode的UTF-8,它的大小也没有那么糟,并且在需要时可以很好地压缩。但是真正的好处是我的代码很漂亮,干净而且易于理解,并且对JSON请求有效载荷的关注也没有太大的麻烦。

那么,为什么不是所有人都会简单地将Base64编码为JSON文件而不使用multipart / form-data呢?有标准,但是这些标准确实会经常更改。无论如何,标准真的只是建议吗?

Answers:


16

multipart/form-data是为HTML表单创建的结构。正如您所发现的,肯定的multipart/form-data是,传输大小更接近于要传输的对象的大小-在对象的文本编码中,该大小大大膨胀了。您可以理解,当发明该协议时,Internet带宽比CPU周期更有价值。

根据互联网,我应该使用多部分/表单数据请求

multipart/form-data是所有浏览器都支持的最佳浏览器上传协议。没有理由将其用于服务器到服务器的通信。服务器到服务器的通信通常不是基于表单的。通信对象更加复杂,并且需要嵌套和类型-JSON处理得很好。Base64编码是用于以您选择的任何序列化格式传输二进制对象的简单解决方案。诸如CBORBSON之类的二进制协议甚至更好,因为它们可以序列化为比Base64小的对象,并且它们与JSON足够接近,因此(应该)是对现有JSON通信的轻松扩展。不确定CPU性能与Base64。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.