Sto lavorando in C # e sto comunicando tra 2 app che sto scrivendo. Mi è piaciuta l'API Web e JSON. Ora sono nel 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 manualmente una richiesta che segue un formato simile al seguente:
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 angosciante per qualcuno che è abituato ai bei dati JSON. Quindi ovviamente la soluzione è creare una richiesta JSON e Base64 codifica il file e finisce con una richiesta come questa:
{
"field1":"Joe Blow",
"fileImage":"JVBERi0xLjUKJe..."
}
E possiamo utilizzare la serializzazione e la deserializzazione JSON ovunque desideriamo. Inoltre, il codice per inviare questi dati è abbastanza semplice. È sufficiente creare la classe per la serializzazione JSON e quindi impostare le proprietà. La proprietà della stringa di file è impostata in poche righe banali:
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);
}
Niente più delimitatori e intestazioni sciocchi per ogni oggetto. 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 50 KB a 1,5 MB circa. Per prima cosa ho scritto alcune righe per semplicemente lo streaming del file in un array di byte per confrontarlo con la logica che scorre nel file e poi lo converte in un flusso Base64. Di seguito sono riportati i 2 blocchi di codice che ho profilato:
Stream diretto al profilo 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
Streaming ed Encode per profilare la creazione di una 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 furono che la lettura semplice impiegava sempre 0ms, ma che la codifica Base64 impiegava fino a 5ms. Di seguito sono riportati i tempi più lunghi:
File Size | Output Stream Size | Time
1352KB 1802KB 5ms
1031KB 1374KB 7ms
463KB 617KB 1ms
Tuttavia, in produzione non scriveresti mai ciecamente dati multipart / form senza prima controllare il delimitatore giusto? Quindi ho modificato il codice dei dati del modulo in modo che controllasse i byte delimitatori nel file stesso per assicurarsi che tutto sarebbe stato analizzato correttamente. Non ho scritto un algoritmo di scansione ottimizzato, quindi ho solo ridotto il delimitatore in modo da non perdere 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à significativamente più lento. Di seguito sono riportati i risultati con tempi> 0 ms 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 è il vantaggio prestazionale di fare una codifica Base64 invece di controllare i byte di file per un delimitatore.
Ovviamente la codifica Base64 aumenterà le dimensioni come mostrato nella prima tabella, ma non è poi così male anche con UTF-8 compatibile con Unicode e comprimerebbe bene se lo si desidera. Ma il vero vantaggio è che il mio codice è carino, pulito e facilmente comprensibile e non fa male ai miei occhi guardare così tanto il payload della richiesta JSON.
Quindi perché mai qualcuno non dovrebbe semplicemente codificare i file Base64 in JSON invece di utilizzare multipart / form-data? Ci sono gli standard, ma questi cambiano relativamente spesso. Gli standard sono davvero solo suggerimenti, giusto?