web-dev-qa-db-ja.com

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

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

各アイテムの愚かな区切り文字とヘッダーはもうありません。残りの問題はパフォーマンスです。だから私はそれをプロファイルした。 50KBから1.5MB程度の範囲でネットワーク経由で送信する必要がある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

しかし、本番環境では、最初に区切り文字を確認せずにマルチパート/フォームデータを盲目的に書き込むことは決してないでしょうか?したがって、フォームデータコードを変更して、ファイル自体の区切りバイトをチェックして、すべてが正常に解析されることを確認しました。最適化されたスキャンアルゴリズムを作成しなかったので、区切り文字を小さくして、多くの時間を無駄にしないようにしました。

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を使用する代わりに、Base64でJSONにファイルをエンコードしないのでしょうか。標準がありますが、これらは比較的頻繁に変更されます。規格はとにかく本当に単なる提案ですよね?

14
Ian

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

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

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

16
Samuel