Warum sollte jemand Multipart- / Formulardaten für gemischte Daten- und Dateiübertragungen verwenden?

14

Ich arbeite in C # und kommuniziere zwischen zwei Apps, die ich schreibe. Ich mag die Web-API und JSON. Jetzt bin ich an dem Punkt, an dem ich eine Routine schreibe, um einen Datensatz zwischen den beiden Servern zu senden, der einige Textdaten und eine Datei enthält.

Laut Internet soll ich eine mehrteilige / Formulardatenanfrage verwenden, wie hier gezeigt:

SO Frage "Mehrteilige Formulare vom C # -Client"

Grundsätzlich schreiben Sie eine Anfrage manuell in folgendem Format:

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--

Kopiert aus RFC 1867 - Formularbasierter Dateiupload in HTML

Dieses Format ist für jemanden, der an nette JSON-Daten gewöhnt ist, ziemlich belastend. Daher besteht die Lösung offensichtlich darin, eine JSON-Anfrage zu erstellen und die Datei mit Base64 zu kodieren und am Ende eine Anfrage wie die folgende zu erhalten:

{
    "field1":"Joe Blow",
    "fileImage":"JVBERi0xLjUKJe..."
}

Und wir können die JSON-Serialisierung und -Deserialisierung überall einsetzen, wo wir möchten. Darüber hinaus ist der Code zum Senden dieser Daten recht einfach. Sie erstellen einfach Ihre Klasse für die JSON-Serialisierung und legen dann die Eigenschaften fest. Die Datei-String-Eigenschaft wird in wenigen einfachen Zeilen festgelegt:

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);
}

Keine albernen Begrenzer und Überschriften mehr für jedes Element. Jetzt ist die verbleibende Frage die Leistung. Also habe ich das profiliert. Ich habe einen Satz von 50 Beispieldateien, die ich über das Kabel senden müsste und die zwischen 50 KB und 1,5 MB liegen. Zuerst habe ich einige Zeilen geschrieben, um die Datei einfach in ein Byte-Array zu streamen, um dies mit der Logik zu vergleichen, die in der Datei streamt und sie dann in einen Base64-Stream konvertiert. Unten sind die 2 Codestücke, die ich profiliert habe:

Direkter Stream zum Profil mehrteiliger / Formulardaten

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 und Codierung in ein Profil zur Erstellung einer JSON-Anfrage

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

Das Ergebnis war, dass das einfache Lesen immer 0 ms dauerte, die Base64-Codierung jedoch bis zu 5 ms dauerte. Unten sind die längsten Zeiten:

File Size  |  Output Stream Size  |  Time
1352KB        1802KB                 5ms
1031KB        1374KB                 7ms
463KB         617KB                  1ms

In der Produktion würden Sie jedoch niemals einfach blind Multipart- / Formulardaten schreiben, ohne vorher Ihr Trennzeichen zu überprüfen, oder? Deshalb habe ich den Formulardatencode so geändert, dass er die Begrenzungsbytes in der Datei selbst überprüft, um sicherzustellen, dass alles in Ordnung analysiert wird. Ich habe keinen optimierten Scan-Algorithmus geschrieben, deshalb habe ich den Begrenzer nur klein gemacht, damit nicht viel Zeit verschwendet wird.

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;

Jetzt zeigen mir die Ergebnisse, dass die Formulardatenmethode tatsächlich deutlich langsamer sein wird. Nachfolgend sind Ergebnisse mit Zeiten> 0 ms für beide Methoden aufgeführt:

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

Es scheint nicht, dass ein optimierter Algorithmus viel besser wäre, da mein Begrenzer nur 5 Zeichen lang war. Trotzdem nicht dreimal besser, was der Leistungsvorteil einer Base64-Codierung ist, anstatt die Dateibytes auf einen Begrenzer zu überprüfen.

Offensichtlich wird die Base64-Codierung die Größe erhöhen, wie ich in der ersten Tabelle gezeigt habe, aber es ist wirklich nicht so schlimm, selbst mit Unicode-fähigem UTF-8 und würde auf Wunsch gut komprimieren. Aber der eigentliche Vorteil ist, dass mein Code schön und sauber und leicht verständlich ist und es meinen Augen nicht schadet, die JSON-Anforderungsnutzdaten so genau zu betrachten.

Warum um alles in der Welt sollte jemand nicht einfach Dateien in JSON codieren, anstatt Multipart- / Formulardaten zu verwenden? Es gibt Standards, die sich jedoch relativ oft ändern. Normen sind doch wirklich nur Vorschläge, oder?

Ian
quelle

Antworten:

15

multipart/form-dataist ein Konstrukt, das für HTML-Formulare erstellt wurde. Wie Sie das Positive von entdeckt habenmultipart/form-data Übertragungsgröße eher der Größe des zu übertragenden Objekts. Bei einer Textcodierung des Objekts wird die Größe erheblich vergrößert. Sie können verstehen, dass die Internetbandbreite bei der Erfindung des Protokolls ein wertvolleres Gut war als die CPU-Zyklen.

Laut Internet soll ich eine mehrteilige / Formulardatenanfrage verwenden

multipart/form-dataist das beste Protokoll für Browser-Uploads, da es von allen Browsern unterstützt wird. Es gibt keinen Grund, es für die Server-zu-Server-Kommunikation zu verwenden. Die Kommunikation von Server zu Server erfolgt normalerweise nicht formularbasiert. Kommunikationsobjekte sind komplexer und erfordern Verschachtelung und Typen - die Anforderungen, mit denen JSON gut umgeht. Die Base64-Codierung ist eine einfache Lösung zum Übertragen von Binärobjekten in einem beliebigen Serialisierungsformat. Binäre Protokolle wie CBOR oder BSON sind sogar noch besser, da sie für kleinere Objekte als Base64 serialisiert werden und JSON nahe genug sind, um eine einfache Erweiterung einer vorhandenen JSON-Kommunikation zu sein. Ich bin mir nicht sicher, wie die CPU-Leistung im Vergleich zu Base64 ist.

Samuel
quelle