Я работаю в C # и поддерживаю связь между двумя приложениями, которые я пишу. Мне понравились Web API и JSON. Сейчас я нахожусь в точке, где я пишу процедуру для отправки записи между двумя серверами, которая включает в себя некоторые текстовые данные и файл.
Согласно Интернету, я должен использовать запрос 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);
}
Нет больше глупых разделителей и заголовков для каждого элемента. Теперь оставшийся вопрос - производительность. Так что я это профилировал. У меня есть набор из 50 файлов примеров, которые мне нужно будет отправить по проводам, в диапазоне от 50 КБ до 1,5 МБ или около того. Сначала я написал несколько строк, чтобы просто передать файл в байтовый массив, чтобы сравнить его с логикой, которая передается в файл, а затем преобразовал его в поток 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;
Теперь результаты показывают мне, что метод данных формы будет значительно медленнее. Ниже приведены результаты со временем> 0 мс для любого метода:
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 символов. В любом случае, не в три раза лучше, что является преимуществом в производительности при выполнении кодирования Base64 вместо проверки байтов файла на наличие разделителя.
Очевидно, что кодировка Base64 будет увеличивать размер, как я показываю в первой таблице, но на самом деле это не так уж плохо, даже с UTF-8, поддерживающим Unicode, и при желании хорошо сжимается. Но реальным преимуществом является то, что мой код приятный, чистый и легко понятный, и мне не мешает смотреть на полезную нагрузку JSON-запроса.
Так с какой стати кто-то не просто кодирует файлы Base64 в JSON вместо использования multipart / form-data? Стандарты есть, но они меняются относительно часто. Стандарты - это всего лишь предложения, верно?