Я працюю в 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? Існують стандарти, але вони змінюються відносно часто. Стандарти - це справді просто пропозиції, правда?