Estou trabalhando em c # e fazendo alguma comunicação entre dois aplicativos que estou escrevendo. Passei a gostar da API da Web e JSON. Agora estou no ponto em que estou escrevendo uma rotina para enviar um registro entre os dois servidores que inclui alguns dados de texto e um arquivo.
De acordo com a Internet, devo usar uma solicitação multipart / form-data, como mostrado aqui:
SO Pergunta "Formulários de várias partes do cliente C #"
Basicamente, você escreve uma solicitação manualmente que segue um formato como este:
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--
Copiado do RFC 1867 - Upload de arquivo baseado em formulário em HTML
Esse formato é bastante angustiante para alguém que está acostumado a obter bons dados JSON. Então, obviamente, a solução é criar uma solicitação JSON e o Base64 codificar o arquivo e terminar com uma solicitação como esta:
{
"field1":"Joe Blow",
"fileImage":"JVBERi0xLjUKJe..."
}
E podemos usar a serialização e desserialização JSON em qualquer lugar que desejarmos. Além disso, o código para enviar esses dados é bastante simples. Você acabou de criar sua classe para serialização JSON e, em seguida, defina as propriedades. A propriedade string do arquivo é configurada em algumas linhas triviais:
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);
}
Não há mais delimitadores e cabeçalhos tolos para cada item. Agora, a questão restante é desempenho. Então eu perfilei isso. Eu tenho um conjunto de 50 arquivos de exemplo que eu precisaria enviar através do fio que varia de 50 KB a 1,5 MB ou mais. Primeiro, escrevi algumas linhas para simplesmente transmitir o arquivo para uma matriz de bytes para comparar isso com a lógica que transmite no arquivo e depois o converte em um fluxo Base64. Abaixo estão os 2 pedaços de código que eu criei perfil:
Stream direto para o perfil 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
Transmita e Codifique no perfil criando a solicitação 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
Os resultados foram que a leitura simples sempre levava 0ms, mas a codificação Base64 demorava 5ms. Abaixo estão os tempos mais longos:
File Size | Output Stream Size | Time
1352KB 1802KB 5ms
1031KB 1374KB 7ms
463KB 617KB 1ms
No entanto, na produção, você nunca escreveria cegamente dados de várias partes / formulários sem primeiro verificar seu delimitador, certo? Então, modifiquei o código de dados do formulário para verificar os bytes delimitadores no próprio arquivo para garantir que tudo seria analisado corretamente. Como não escrevi um algoritmo de varredura otimizado, reduzi o delimitador para que não perdesse muito tempo.
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;
Agora, os resultados estão me mostrando que o método form-data será realmente significativamente mais lento. Abaixo estão os resultados com tempos> 0ms para qualquer um dos métodos:
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
Não parece que um algoritmo otimizado se sairia muito melhor, visto que meu delimitador tinha apenas 5 caracteres. De qualquer forma, não é 3x melhor, que é a vantagem de desempenho de se fazer uma codificação Base64 em vez de verificar se há um delimitador nos bytes do arquivo.
Obviamente, a codificação Base64 aumentará o tamanho, como mostro na primeira tabela, mas não é tão ruim assim, mesmo com UTF-8 com capacidade para Unicode e seria compactada bem, se desejado. Mas o benefício real é que meu código é agradável, limpo e facilmente compreensível, e não machuca meus olhos olhar tanto para a carga útil de solicitação JSON.
Então, por que diabos alguém não simplesmente codificaria o Base64 em JSON, em vez de usar multipart / form-data? Existem padrões, mas estes mudam com relativa frequência. Padrões são realmente apenas sugestões de qualquer maneira, certo?