Mengapa ada orang yang menggunakan multipart / formulir-data untuk data campuran dan transfer file?

14

Saya bekerja di C # dan melakukan komunikasi antara 2 aplikasi yang saya tulis. Saya menyukai API Web dan JSON. Sekarang saya pada titik di mana saya menulis rutin untuk mengirim catatan antara dua server yang mencakup beberapa data teks dan file.

Menurut internet saya seharusnya menggunakan permintaan multipart / form-data seperti yang ditunjukkan di sini:

SO Pertanyaan "Formulir multi-bagian dari C # client"

Pada dasarnya Anda menulis permintaan secara manual yang mengikuti format seperti ini:

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--

Disalin dari RFC 1867 - Unggahan File Berbasis Formulir dalam HTML

Format ini cukup menyedihkan bagi seseorang yang terbiasa dengan data JSON yang bagus. Jadi jelas solusinya adalah membuat permintaan JSON dan Base64 menyandikan file dan berakhir dengan permintaan seperti ini:

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

Dan kita dapat menggunakan serialisasi dan deserialisasi JSON di mana saja kita mau. Selain itu, kode untuk mengirim data ini cukup sederhana. Anda cukup membuat kelas Anda untuk serialisasi JSON dan kemudian mengatur properti. Properti string file diatur dalam beberapa baris sepele:

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

Tidak ada lagi pembatas dan header konyol untuk setiap item. Sekarang pertanyaan yang tersisa adalah kinerja. Jadi saya membuat profil itu. Saya memiliki satu set 50 file sampel yang saya perlukan untuk dikirim melalui kabel yang berkisar dari 50KB hingga 1,5MB atau lebih. Pertama saya menulis beberapa baris untuk melakukan streaming dalam file ke array byte untuk membandingkannya dengan logika yang stream dalam file dan kemudian mengubahnya menjadi stream Base64. Di bawah ini adalah 2 potongan kode yang saya profil:

Aliran Langsung ke Profil multipart / formulir-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

Streaming dan Enkode ke profil yang membuat permintaan 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

Hasilnya adalah bahwa pembacaan sederhana selalu mengambil 0ms, tetapi bahwa pengkodean Base64 memakan waktu hingga 5ms. Di bawah ini adalah waktu terlama:

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

Namun, dalam produksi Anda tidak akan pernah secara buta menulis data multi-formulir / formulir tanpa terlebih dahulu memeriksa pembatas Anda, bukan? Jadi saya memodifikasi kode form-data sehingga memeriksa byte pembatas dalam file itu sendiri untuk memastikan semuanya akan diuraikan ok. Saya tidak menulis algoritma pemindaian yang dioptimalkan, jadi saya hanya membuat pembatas kecil sehingga tidak akan membuang banyak waktu.

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;

Sekarang hasilnya menunjukkan kepada saya bahwa metode form-data akan benar-benar lebih lambat. Di bawah ini adalah hasil dengan waktu> 0 ms untuk kedua metode:

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

Tampaknya tidak ada algoritma yang dioptimalkan akan jauh lebih baik melihat karena pembatas saya hanya 5 karakter. Tidak 3x lebih baik lagi, yang merupakan keuntungan kinerja melakukan pengkodean Base64 daripada memeriksa byte file untuk pembatas.

Jelas pengkodean Base64 akan mengembang ukurannya seperti yang saya tunjukkan di tabel pertama, tapi itu benar-benar tidak buruk bahkan dengan Unicode mampu UTF-8 dan akan memampatkan dengan baik jika diinginkan. Tetapi manfaat sebenarnya adalah kode saya bagus dan bersih dan mudah dimengerti dan tidak ada salahnya untuk melihat beban permintaan JSON sebanyak itu.

Jadi mengapa ada orang yang tidak hanya Base64 menyandikan file di JSON daripada menggunakan multipart / form-data? Ada Standar, tetapi ini sering berubah relatif. Standar benar-benar hanya saran, kan?

Ian
sumber

Jawaban:

16

multipart/form-dataadalah konstruk yang dibuat untuk formulir HTML. Ketika Anda telah menemukan positif dari multipart/form-dataukuran transfer lebih dekat dengan ukuran objek yang ditransfer - di mana dalam teks penyandian objek ukuran meningkat secara substansial. Anda dapat memahami bahwa bandwidth internet adalah komoditas yang lebih berharga daripada siklus CPU ketika protokol ditemukan.

Menurut internet saya seharusnya menggunakan permintaan multi-formulir / data

multipart/form-dataadalah protokol terbaik untuk unggahan peramban karena didukung oleh semua peramban. Tidak ada alasan untuk menggunakannya untuk komunikasi server-ke-server. Komunikasi server-ke-server biasanya tidak berbasis form. Objek komunikasi lebih kompleks dan membutuhkan bersarang dan jenis - persyaratan yang JSON tangani dengan baik. Pengkodean Base64 adalah solusi sederhana untuk mentransfer objek biner dalam format serialisasi apa pun yang Anda pilih. Protokol biner seperti CBOR atau BSON bahkan lebih baik karena mereka membuat serial ke objek yang lebih kecil dari Base64, dan mereka cukup dekat dengan JSON sehingga (seharusnya) merupakan ekstensi yang mudah untuk komunikasi JSON yang ada. Tidak yakin tentang kinerja CPU vs. Base64.

Samuel
sumber