Чому хтось повинен використовувати багаточастинні / формулярні дані для змішаних даних та передачі файлів?


14

Я працюю в C # і спілкуюся між двома програмами, про які пишу. Мені подобаються Web API та JSON. Тепер я перебуваю в точці, коли я пишу розпорядок надсилати запис між двома серверами, який включає деякі текстові дані та файл.

Згідно з Інтернетом, я повинен використовувати запит на багатосторонню форму / дані, як показано тут:

ТАК Питання "Багаточастинні форми від клієнта 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 зразкових файлів, які мені потрібно було б надіслати через провід, що становить від 50 КБ до 1,5 Мб або близько того. Спочатку я написав рядки, щоб просто передати файл у байтовий масив, щоб порівняти його з логікою, яка потокує у файлі, а потім перетворить його на потік Base64. Нижче наведено два фрагменти коду, який я профілював:

Прямий потік у профіль багаточастинних / форм-даних

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 збільшить розмір, як я показую в першій таблиці, але це насправді не так вже й погано, навіть якщо UTF-8 підтримує Unicode, і при бажанні добре стиснеться. Але справжня користь - мій код приємний і чистий і легко зрозумілий, і це не заважає моїм очним яблукам дивитися на корисну навантаження JSON-запиту.

То чому б на землі хтось не просто Base64 кодував файли в JSON, а не використовував дані multipart / form-data? Існують стандарти, але вони змінюються відносно часто. Стандарти - це справді просто пропозиції, правда?

Відповіді:


16

multipart/form-dataце конструкція, створена для форм HTML. Як ви виявили, позитивним multipart/form-dataє те, що розмір передачі ближче до розміру об'єкта, що передається - де в текстовому кодуванні об'єкта розмір істотно завищений. Ви можете зрозуміти, що пропускна здатність Інтернету була ціннішим товаром, ніж цикли процесора, коли був винайдений протокол.

Згідно з Інтернетом, я повинен використовувати запит на багатосторонню форму / дані

multipart/form-dataє найкращим протоколом для завантаження браузера, оскільки він підтримується всіма браузерами. Немає підстав використовувати його для зв'язку між сервером та сервером. Зв'язок між сервером і сервером, як правило, не заснований на формі. Об'єкти зв'язку складніші і потребують гніздування та типів - вимоги, з якими добре впорається JSON. Кодування Base64 - це просте рішення для передачі бінарних об'єктів у будь-якому обраному вами форматі серіалізації. Бінарні протоколи, такі як CBOR або BSON , ще кращі, оскільки вони серіалізуються на менші об'єкти, ніж Base64, і вони досить близькі до JSON, що це (повинно бути) легким розширенням до існуючого зв'язку JSON. Не впевнені в продуктивності процесора порівняно з Base64.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.