私はC#で作業しており、作成中の2つのアプリ間で通信を行っています。Web APIとJSONが好きになりました。現在、2つのサーバー間でテキストデータとファイルを含むレコードを送信するルーチンを作成しています。
インターネットによると、ここに示すようにmultipart / form-dataリクエストを使用することになっています。
基本的に、次のような形式に従ってリクエストを手動で記述します。
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エンコードしないのでしょうか?規格はありますが、これらは比較的頻繁に変更されます。とにかく、規格は本当に単なる提案にすぎませんか?