データとファイルの混合転送にmultipart / form-dataを使用するのはなぜですか?


14

私はC#で作業しており、作成中の2つのアプリ間で通信を行っています。Web APIとJSONが好きになりました。現在、2つのサーバー間でテキストデータとファイルを含むレコードを送信するルーチンを作成しています。

インターネットによると、ここに示すように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 KBから1.5 MB程度の範囲でネットワーク経由で送信する必要がある50のサンプルファイルのセットがあります。最初に、ファイルをバイト配列に単純にストリームするいくつかの行を作成し、それをファイルでストリームしてからBase64ストリームに変換するロジックと比較します。以下に、プロファイリングした2つのコードチャンクを示します。

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);
}
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

その結果、単純な読み取りには常に0ミリ秒かかりましたが、Base64エンコードには最大5ミリ秒かかりました。以下が最長時間です。

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リクエストペイロードをそれほど見なくても目障りではありません。

では、一体なぜ、multipart / form-dataを使用するのではなく、単にJSONでファイルをBase64エンコードしないのでしょうか?規格はありますが、これらは比較的頻繁に変更されます。とにかく、規格は本当に単なる提案にすぎませんか?

回答:


16

multipart/form-dataHTMLフォーム用に作成された構成体です。ポジティブなのはmultipart/form-data、転送サイズが転送されるオブジェクトのサイズに近いことです。オブジェクトのテキストエンコーディングでは、サイズが大幅に大きくなります。プロトコルが発明されたとき、インターネット帯域幅はCPUサイクルよりも価値のある商品であったことを理解できます。

インターネットによると、私はmultipart / form-dataリクエストを使用することになっています

multipart/form-dataすべてのブラウザでサポートされているため、ブラウザのアップロードに最適なプロトコルです。サーバー間通信に使用する理由はありません。通常、サーバー間通信はフォームベースではありません。通信オブジェクトはより複雑で、ネストと型が必要です。これはJSONが適切に処理する要件です。Base64エンコーディングは、選択したシリアル化形式でバイナリオブジェクトを転送するためのシンプルなソリューションです。CBORBSONなどのバイナリプロトコルはBase64よりも小さなオブジェクトにシリアル化されるため、さらに優れています。また、JSONに十分に近いため、既存のJSON通信を簡単に拡張できます。CPUのパフォーマンスとBase64についてはわかりません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.