Sto lavorando in C # e sto facendo alcune comunicazioni tra 2 app che sto scrivendo. Sono giunto ad apprezzare l'API Web e JSON. Ora sono al punto in cui sto scrivendo una routine per inviare un record tra i due server che include alcuni dati di testo e un file.
Secondo Internet dovrei usare una richiesta multipart / form-data come mostrato qui:
Domanda SO "Moduli multipart da client C #"
Fondamentalmente scrivi una richiesta manualmente che segue un formato come questo:
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--
Copiato da RFC 1867 - Caricamento file basato su modulo in HTML
Questo formato è abbastanza doloroso per qualcuno che è abituato a dati JSON. Quindi ovviamente la soluzione è creare una richiesta JSON e Base64 codifica il file e finire con una richiesta come questa:
{
"field1":"Joe Blow",
"fileImage":"JVBERi0xLjUKJe..."
}
E possiamo usare la serializzazione e deserializzazione JSON ovunque vorremmo. Inoltre, il codice per inviare questi dati è abbastanza semplice. Devi solo creare la tua classe per la serializzazione JSON e quindi impostare le proprietà. La proprietà della stringa di file è impostata in poche righe:
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);
}
Nessun delimitatore e intestazione più sciocchi per ogni elemento. Ora la domanda rimanente è la prestazione. Quindi l'ho profilato. Ho un set di 50 file di esempio che avrei bisogno di inviare attraverso il cavo che vanno da 50KB a 1,5 MB o giù di lì. Per prima cosa ho scritto alcune righe per eseguire semplicemente lo streaming nel file su una matrice di byte per confrontarlo con la logica che scorre nel file e quindi convertirlo in un flusso Base64. Di seguito sono riportati i 2 blocchi di codice che ho profilato:
Direct Stream to Profile 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
Riproduci lo stream e codifica il profilo creando la richiesta 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
I risultati erano che la lettura semplice richiedeva sempre 0ms, ma che la codifica Base64 richiedeva fino a 5 ms. Di seguito sono riportati i tempi più lunghi:
File Size | Output Stream Size | Time
1352KB 1802KB 5ms
1031KB 1374KB 7ms
463KB 617KB 1ms
Tuttavia, nella produzione non si scriverebbero mai solo i dati multipart / form senza aver prima controllato correttamente il delimitatore? Così ho modificato il codice del modulo in modo che controllasse i byte delimitatore nel file stesso per assicurarmi che tutto fosse analizzato ok. Non ho scritto un algoritmo di scansione ottimizzato, quindi ho appena ridotto il delimitatore in modo che non sprecherebbe molto 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;
Ora i risultati mi mostrano che il metodo form-data sarà in realtà molto più lento. Di seguito sono riportati i risultati con tempi > 0ms per entrambi i metodi:
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
Non sembra che un algoritmo ottimizzato farebbe molto meglio visto che il mio delimitatore era lungo solo 5 caratteri. Non è comunque 3 volte migliore, il che rappresenta il vantaggio in termini di prestazioni di eseguire una codifica Base64 invece di controllare i byte del file per un delimitatore.
Ovviamente la codifica Base64 si gonfierà come mostrato nella prima tabella, ma non è poi così male nemmeno con UTF-8 in grado di Unicode e si comprimerebbe bene se lo si desidera. Ma il vero vantaggio è che il mio codice è bello, pulito e facilmente comprensibile e non fa male ai miei occhi per guardare tanto al carico utile della richiesta JSON.
Quindi perché mai nessuno dovrebbe semplicemente codificare Base64 in JSON invece di usare multipart / form-data? Ci sono gli standard, ma questi cambiano relativamente spesso. Gli standard sono davvero solo suggerimenti, comunque, giusto?