Estoy trabajando en C # y estoy haciendo alguna comunicación entre 2 aplicaciones que estoy escribiendo. Me han llegado a gustar la API web y JSON. Ahora estoy en el punto donde estoy escribiendo una rutina para enviar un registro entre los dos servidores que incluye algunos datos de texto y un archivo.
De acuerdo con Internet, se supone que debo usar una solicitud multiparte / datos de formulario como se muestra aquí:
Pregunta SO "Formularios multiparte del cliente C #"
Básicamente, escribe una solicitud manualmente que sigue un 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 de RFC 1867 - Carga de archivos basada en formularios en HTML
Este formato es bastante angustioso para alguien que está acostumbrado a buenos datos JSON. Entonces, obviamente, la solución es crear una solicitud JSON y Base64 codificar el archivo y terminar con una solicitud como esta:
{
"field1":"Joe Blow",
"fileImage":"JVBERi0xLjUKJe..."
}
Y podemos hacer uso de la serialización y deserialización JSON en cualquier lugar que deseemos. Además de eso, el código para enviar estos datos es bastante simple. Simplemente cree su clase para la serialización JSON y luego configure las propiedades. La propiedad de cadena de archivo se establece en unas pocas líneas triviales:
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);
}
No más delimitadores tontos y encabezados para cada elemento. Ahora la pregunta restante es el rendimiento. Así que lo describí. Tengo un conjunto de 50 archivos de muestra que necesitaría enviar a través del cable que van desde 50 KB a 1,5 MB más o menos. Primero escribí algunas líneas para simplemente transmitir en el archivo a una matriz de bytes para comparar eso con la lógica que se transmite en el archivo y luego convertirlo en una secuencia de Base64. A continuación se muestran los 2 fragmentos de código que perfilé:
Transmisión directa al perfil multiparte / datos de formulario
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
Transmitir y codificar al perfil creando solicitud 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
Los resultados fueron que la lectura simple siempre tomó 0 ms, pero que la codificación Base64 tomó hasta 5 ms. A continuación se muestran los tiempos más largos:
File Size | Output Stream Size | Time
1352KB 1802KB 5ms
1031KB 1374KB 7ms
463KB 617KB 1ms
Sin embargo, en la producción nunca escribiría a ciegas datos multiparte / formulario sin verificar primero su delimitador, ¿verdad? Así que modifiqué el código de datos de formulario para que verificara los bytes delimitadores en el archivo en sí para asegurarme de que todo se analizara correctamente. No escribí un algoritmo de escaneo optimizado, así que hice el delimitador pequeño para que no perdiera mucho tiempo.
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;
Ahora los resultados me muestran que el método de datos de formulario en realidad será significativamente más lento. A continuación se muestran resultados con tiempos> 0 ms para cualquier método:
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
No parece que un algoritmo optimizado sea mucho mejor ya que mi delimitador tiene solo 5 caracteres. De todos modos, no es 3 veces mejor, que es la ventaja de rendimiento de hacer una codificación Base64 en lugar de verificar los bytes del archivo para un delimitador.
Obviamente, la codificación Base64 inflará el tamaño como lo muestro en la primera tabla, pero realmente no es tan malo incluso con UTF-8 con capacidad Unicode y se comprimiría bien si lo desea. Pero el beneficio real es que mi código es agradable y limpio y fácilmente comprensible, y tampoco perjudica a mis ojos mirar la carga útil de la solicitud JSON.
Entonces, ¿por qué alguien no codificaría simplemente los archivos Base64 en JSON en lugar de usar multipart / form-data? Existen los Estándares, pero estos cambian con relativa frecuencia. Los estándares son realmente sugerencias de todos modos, ¿verdad?