Pourquoi quelqu'un utiliserait-il plusieurs parties / formulaires pour des transferts de données et de fichiers mixtes?

14

Je travaille en C # et je fais la communication entre 2 applications que j'écris. J'en suis venu à aimer l'API Web et JSON. Maintenant, j'en suis au point où j'écris une routine pour envoyer un enregistrement entre les deux serveurs qui inclut des données texte et un fichier.

Selon Internet, je suis censé utiliser une demande multipart / form-data comme indiqué ici:

Question SO "Formulaires en plusieurs parties du client C #"

Fondamentalement, vous écrivez une demande manuellement qui suit un format comme celui-ci:

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

Copié de RFC 1867 - Téléchargement de fichier basé sur un formulaire en HTML

Ce format est assez pénible pour quelqu'un qui est habitué aux belles données JSON. Donc, évidemment, la solution est de créer une requête JSON et Base64 encoder le fichier et se retrouver avec une requête comme celle-ci:

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

Et nous pouvons utiliser la sérialisation et la désérialisation JSON où nous le souhaitons. En plus de cela, le code pour envoyer ces données est assez simple. Vous créez simplement votre classe pour la sérialisation JSON, puis définissez les propriétés. La propriété de chaîne de fichier est définie en quelques lignes 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);
}

Plus de délimiteurs ni d'en-têtes idiots pour chaque élément. Maintenant, la question restante est la performance. J'ai donc décrit cela. J'ai un ensemble de 50 exemples de fichiers que j'aurais besoin d'envoyer à travers le fil qui vont de 50 Ko à 1,5 Mo environ. J'ai d'abord écrit quelques lignes pour simplement diffuser le fichier dans un tableau d'octets pour le comparer à la logique qui diffuse dans le fichier, puis le convertir en un flux Base64. Voici les 2 morceaux de code que j'ai profilés:

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

Diffuser et encoder dans le profil créant une demande 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

Les résultats ont été que la lecture simple prenait toujours 0 ms, mais que l'encodage Base64 prenait jusqu'à 5 ms. Voici les temps les plus longs:

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

Cependant, en production, vous n'écrivez jamais aveuglément en plusieurs parties / données de formulaire sans d'abord vérifier votre délimiteur, n'est-ce pas? J'ai donc modifié le code de données de formulaire afin qu'il vérifie les octets de délimitation dans le fichier lui-même pour s'assurer que tout serait analysé correctement. Je n'ai pas écrit d'algorithme de numérisation optimisé, j'ai donc simplement réduit le délimiteur pour qu'il ne perde pas beaucoup de temps.

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;

Maintenant, les résultats me montrent que la méthode des données de formulaire sera en réalité beaucoup plus lente. Voici les résultats avec des temps> 0 ms pour l'une ou l'autre méthode:

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

Il ne semble pas qu'un algorithme optimisé ferait beaucoup mieux, car mon délimiteur ne comptait que 5 caractères. Pas 3 fois mieux de toute façon, ce qui est l'avantage de faire un encodage Base64 au lieu de vérifier les octets de fichier pour un délimiteur.

Évidemment, l'encodage Base64 gonflera la taille comme je le montre dans le premier tableau, mais ce n'est vraiment pas si mal même avec UTF-8 compatible Unicode et se comprimerait bien si vous le souhaitez. Mais le véritable avantage est que mon code est agréable et propre et facilement compréhensible et cela ne fait pas de mal à mes yeux de regarder la charge utile de la demande JSON non plus.

Alors pourquoi diable quelqu'un ne coderait-il pas simplement des fichiers Base64 en JSON au lieu d'utiliser des données en plusieurs parties / formulaires? Il existe des normes, mais celles-ci changent assez souvent. Les normes ne sont en fait que des suggestions, n'est-ce pas?

Ian
la source

Réponses:

16

multipart/form-dataest une construction créée pour les formulaires HTML. Comme vous l'avez découvert, le positif multipart/form-dataest que la taille de transfert est plus proche de la taille de l'objet transféré - où dans un texte codant l'objet, la taille est considérablement gonflée. Vous pouvez comprendre que la bande passante Internet était un bien plus précieux que les cycles CPU lorsque le protocole a été inventé.

Selon Internet, je suis censé utiliser une demande de données en plusieurs parties / formulaire

multipart/form-dataest le meilleur protocole pour les téléchargements de navigateur car il est pris en charge par tous les navigateurs. Il n'y a aucune raison de l'utiliser pour la communication de serveur à serveur. La communication de serveur à serveur n'est généralement pas basée sur un formulaire. Les objets de communication sont plus complexes et nécessitent une imbrication et des types - les exigences que JSON gère bien. L'encodage Base64 est une solution simple pour transférer des objets binaires dans le format de sérialisation que vous choisissez. Les protocoles binaires comme CBOR ou BSON sont encore meilleurs car ils sérialisent vers des objets plus petits que Base64, et ils sont suffisamment proches de JSON pour qu'il (devrait être) une extension facile d'une communication JSON existante. Pas sûr des performances du processeur par rapport à Base64.

Samuel
la source