application / x-www-form-urlencoded ou multipart / form-data?

1336

Dans HTTP, il existe deux façons de POSTER des données: application/x-www-form-urlencodedet multipart/form-data. Je comprends que la plupart des navigateurs ne peuvent télécharger des fichiers que s'ils multipart/form-datasont utilisés. Existe-t-il des conseils supplémentaires sur l'utilisation d'un des types de codage dans un contexte d'API (aucun navigateur impliqué)? Cela pourrait par exemple être basé sur:

  • taille des données
  • existence de caractères non ASCII
  • existence sur des données binaires (non codées)
  • la nécessité de transférer des données supplémentaires (comme le nom de fichier)

Jusqu'à présent, je n'ai trouvé aucune indication formelle sur le Web concernant l'utilisation des différents types de contenu.

max
la source
75
Il convient de mentionner que ce sont les deux types MIME que les formulaires HTML utilisent. HTTP lui-même n'a pas une telle limitation ... on peut utiliser le type MIME qu'il veut via HTTP.
tybro0103

Réponses:

2015

TL; DR

Sommaire; si vous avez des données binaires (non alphanumériques) (ou une charge utile de taille significative) à transmettre, utilisez multipart/form-data. Sinon, utilisez application/x-www-form-urlencoded.


Les types MIME que vous mentionnez sont les deux en- Content-Typetêtes des requêtes HTTP POST que les agents utilisateurs (navigateurs) doivent prendre en charge. Le but de ces deux types de requêtes est d'envoyer une liste de paires nom / valeur au serveur. Selon le type et la quantité de données transmises, l'une des méthodes sera plus efficace que l'autre. Pour comprendre pourquoi, vous devez regarder ce que chacun fait sous les couvertures.

Pour application/x-www-form-urlencoded, le corps du message HTTP envoyé au serveur est essentiellement une chaîne de requête géante - les paires nom / valeur sont séparées par l'esperluette ( &), et les noms sont séparés des valeurs par le symbole égal ( =). Un exemple de ceci serait: 

MyVariableOne=ValueOne&MyVariableTwo=ValueTwo

Selon la spécification :

[Les caractères réservés et] non alphanumériques sont remplacés par «% HH», un signe de pourcentage et deux chiffres hexadécimaux représentant le code ASCII du caractère

Cela signifie que pour chaque octet non alphanumérique qui existe dans l'une de nos valeurs, il faudra trois octets pour le représenter. Pour les gros fichiers binaires, tripler la charge utile sera très inefficace.

C'est là multipart/form-dataqu'intervient. Avec cette méthode de transmission des paires nom / valeur, chaque paire est représentée comme une "partie" dans un message MIME (comme décrit par d'autres réponses). Les parties sont séparées par une limite de chaîne particulière (choisie spécifiquement pour que cette chaîne de limite n'apparaisse dans aucune des charges utiles de "valeur"). Chaque partie a son propre ensemble d'en-têtes MIME comme Content-Type, et en particulier Content-Disposition, qui peut donner à chaque partie son "nom". La valeur de chaque paire nom / valeur est la charge utile de chaque partie du message MIME. La spécification MIME nous donne plus d'options pour représenter la valeur de la charge utile - nous pouvons choisir un encodage plus efficace des données binaires pour économiser la bande passante (par exemple base 64 ou même binaire brut).

Pourquoi ne pas utiliser multipart/form-datatout le temps? Pour les valeurs alphanumériques courtes (comme la plupart des formulaires Web), le surcoût de l'ajout de tous les en-têtes MIME va largement compenser les économies d'un encodage binaire plus efficace.

Matt Bridges
la source
84
Est-ce que x-www-form-urlencoded a une limite de longueur ou est-il illimité?
Pacerier
35
@Pacerier La limite est appliquée par le serveur recevant la requête POST. Voir ce fil pour plus de discussion: stackoverflow.com/questions/2364840/…
Matt Bridges
5
@ZiggyTheHamster JSON et BSON sont chacun plus efficaces pour différents types de données. Base64 est inférieur à gzip, pour les deux méthodes de sérialisation. Base64 n'apporte aucun avantage, HTTP prend en charge les pyloads binaires.
Tiberiu-Ionuț Stan
16
Notez également que si un formulaire contient un téléchargement de fichier nommé, votre seul choix est form-data, car urlencoded n'a pas de moyen de placer le nom de fichier (dans form-data, c'est le paramètre name pour content-disposition).
Guido van Rossum
4
@EML voir ma parenthèse "(choisi spécifiquement pour que cette chaîne limite ne se produise dans aucune des charges utiles" value ")"
Matt Bridges
152

LISEZ AU MOINS LE PREMIER PARA ICI!

Je sais que c'est 3 ans trop tard, mais la réponse (acceptée) de Matt est incomplète et vous causera éventuellement des ennuis. La clé ici est que, si vous choisissez d'utiliser multipart/form-data, la frontière ne doit pas apparaître dans les données de fichier que le serveur reçoit finalement.

Ce n'est pas un problème pour application/x-www-form-urlencoded, car il n'y a pas de frontière. x-www-form-urlencodedpeut également toujours gérer des données binaires, par le simple expédient de transformer un octet arbitraire en trois7BIT octets. Inefficace, mais cela fonctionne (et notez que le commentaire sur l'impossibilité d'envoyer des noms de fichiers ainsi que des données binaires est incorrect; vous l'envoyez simplement comme une autre paire clé / valeur).

Le problème avec multipart/form-dataest que le séparateur de limites ne doit pas être présent dans les données du fichier (voir RFC 2388 ; la section 5.2 inclut également une excuse plutôt boiteuse pour ne pas avoir un type MIME agrégé correct qui évite ce problème).

Donc, à première vue, multipart/form-dataest sans valeur que ce soit dans tout transfert de fichiers, binaire ou autre. Si vous ne choisissez pas correctement votre frontière, vous aurez éventuellement un problème, que vous envoyiez du texte brut ou du binaire brut - le serveur trouvera une frontière au mauvais endroit et votre fichier sera tronqué, ou le POST échouera.

La clé est de choisir un encodage et une frontière de sorte que les caractères de frontière sélectionnés ne puissent pas apparaître dans la sortie encodée. Une solution simple consiste à utiliser base64(ne pas utiliser de binaire brut). Dans base64, 3 octets arbitraires sont codés en quatre caractères de 7 bits, où le jeu de caractères de sortie est [A-Za-z0-9+/=](c'est-à-dire alphanumériques, '+', '/' ou '='). =est un cas particulier et ne peut apparaître qu'à la fin de la sortie codée, en simple =ou en double ==. Maintenant, choisissez votre frontière comme une chaîne ASCII 7 bits qui ne peut pas apparaître dans la base64sortie. De nombreux choix que vous voyez sur le net échouent à ce test - la documentation MDN Forms, par exemple, utilisez "blob" comme limite lors de l'envoi de données binaires - pas bon. Cependant, quelque chose comme "! Blob!" n'apparaîtra jamais en base64sortie.

EML
la source
52
Bien que la prise en compte des données en plusieurs parties / formulaires soit la garantie que la frontière n'apparaît pas dans les données, cela est assez simple à réaliser en choisissant une frontière qui est suffisamment longue. Veuillez ne pas utiliser le codage base64 pour y parvenir. Une limite qui est générée aléatoirement et de la même longueur qu'un UUID devrait être suffisante: stackoverflow.com/questions/1705008/… .
Joshcodes
20
@EML, Cela n'a aucun sens. Évidemment, la limite est choisie automatiquement par le client http (navigateur) et le client sera suffisamment intelligent pour ne pas utiliser une limite qui heurte le contenu de vos fichiers téléchargés. C'est aussi simple qu'une correspondance de sous-chaîne index === -1.
Pacerier
13
@Pacerier: (A) lire la question: "aucun navigateur impliqué, contexte API". (B) les navigateurs ne construisent pas de demandes de toute façon. Vous le faites vous-même, manuellement. Il n'y a pas de magie dans les navigateurs.
EML
12
@BeniBela, Il va probablement suggérer d'utiliser '()+-./:=alors. Pourtant , la génération aléatoire avec sous - chaîne est toujours vérifier le chemin à parcourir et il peut être fait avec une seule ligne: while(true){r = rand(); if(data.indexOf(r) === -1){doStuff();break;}}. La suggestion d'EML (convertir en base64 juste pour éviter de faire correspondre les sous-chaînes) est tout simplement étrange, sans oublier qu'elle s'accompagne d'une dégradation des performances inutile. Et tous les ennuis pour rien puisque l'algorithme d'une ligne est tout aussi simple et simple. Base64 n'est pas censé être (ab) utilisé de cette façon, car le corps HTTP accepte tous les octets de 8 bits .
Pacerier
31
Cette réponse non seulement n'ajoute rien à la discussion, mais donne également de mauvais conseils. Premièrement, chaque fois que des données aléatoires sont transmises dans des parties séparées, il est toujours possible que la limite choisie soit présente dans la charge utile. La SEULE façon de s'assurer que cela ne se produit pas est d'examiner la charge utile entière pour chaque frontière que nous trouvons. Complètement impraticable. Nous acceptons simplement la probabilité infinitésimale d'une collision et trouvons une limite raisonnable, comme "--- limite- <UUID ici> -frontière ---". Deuxièmement, toujours utiliser Base64 gaspillera la bande passante et remplira les tampons sans aucune raison.
vagelis
92

Je ne pense pas que HTTP est limité à POST en multipart ou x-www-form-urlencoded. L'en -tête Content-Type est orthogonal à la méthode HTTP POST (vous pouvez remplir le type MIME qui vous convient). C'est également le cas pour les applications Web basées sur une représentation HTML typique (par exemple, la charge utile json est devenue très populaire pour la transmission de la charge utile pour les demandes ajax).

En ce qui concerne l'API Restful sur HTTP, les types de contenu les plus populaires avec lesquels j'ai été en contact sont application / xml et application / json.

application / xml:

  • taille des données: XML très verbeux, mais généralement pas un problème lors de l'utilisation de la compression et en pensant que le cas d'accès en écriture (par exemple via POST ou PUT) est beaucoup plus rare que l'accès en lecture (dans de nombreux cas, il est <3% de tout le trafic ). Rarement là où j'ai dû optimiser les performances d'écriture
  • existence de caractères non ascii: vous pouvez utiliser utf-8 comme encodage en XML
  • existence de données binaires: il faudrait utiliser le codage base64
  • données de nom de fichier: vous pouvez encapsuler ce champ intérieur en XML

application / json

  • taille des données: plus compacte moins que XML, texte fixe, mais vous pouvez compresser
  • caractères non ascii: json est utf-8
  • données binaires: base64 (voir aussi json-binary-question )
  • données de nom de fichier: encapsuler comme propre section de champ dans json

données binaires comme ressource propre

J'essaierais de représenter les données binaires comme un actif / une ressource propre. Il ajoute un autre appel mais dissocie mieux les choses. Exemples d'images:

POST /images
Content-type: multipart/mixed; boundary="xxxx" 
... multipart data

201 Created
Location: http://imageserver.org/../foo.jpg  

Dans les ressources ultérieures, vous pouvez simplement intégrer la ressource binaire en tant que lien:

<main-resource>
 ...
 <link href="http://imageserver.org/../foo.jpg"/>
</main-resource>
manuel aldana
la source
Intéressant. Mais quand utiliser application / x-www-form-urlencoded et quand multipart / form-data?
max
3
application / x-www-form-urlencoded est le type MIME par défaut de votre demande (voir aussi w3.org/TR/html401/interact/forms.html#h-17.13.4 ). Je l'utilise pour les formulaires Web "normaux". Pour l'API, j'utilise application / xml | json. multipart / form-data est une cloche dans la pensée des pièces jointes (à l'intérieur du corps de réponse, plusieurs sections de données sont concaténées avec une chaîne de limite définie).
manuel aldana
4
Je pense que l'OP posait probablement des questions sur les deux types que les formulaires HTML utilisent, mais je suis content que cela ait été souligné.
tybro0103
30

Je suis d'accord avec beaucoup de choses que Manuel a dites. En fait, ses commentaires se réfèrent à cette url ...

http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4

... quels États:

Le type de contenu "application / x-www-form-urlencoded" est inefficace pour envoyer de grandes quantités de données binaires ou de texte contenant des caractères non ASCII. Le type de contenu "multipart / form-data" doit être utilisé pour soumettre des formulaires contenant des fichiers, des données non ASCII et des données binaires.

Cependant, pour moi, cela se résumerait à un support outil / framework.

  • Avec quels outils et frameworks attendez-vous que vos utilisateurs d'API construisent leurs applications?
  • Ont-ils des cadres ou des composants qu'ils peuvent utiliser qui favorisent une méthode par rapport à l'autre?

Si vous avez une idée claire de vos utilisateurs et de la façon dont ils utiliseront votre API, cela vous aidera à décider. Si vous rendez le téléchargement de fichiers difficile pour vos utilisateurs d'API, ils s'éloigneront, vous passerez beaucoup de temps à les soutenir.

En second lieu, le support d'outil que vous avez pour écrire votre API et la facilité avec laquelle il est possible d'accommoder un mécanisme de téléchargement par rapport à l'autre.

Martin Peck
la source
1
Salut, cela signifie-t-il que chaque fois que nous publions quelque chose sur le serveur Web, nous devons mentionner quel est le type de contenu afin d'informer le serveur Web s'il décode les données? Même si nous créons nous-mêmes la requête http, nous devons mentionner le type de contenu, n'est-ce pas?
GMsoF
2
@GMsoF, c'est facultatif. Voir stackoverflow.com/a/16693884/632951 . Vous pouvez éviter d'utiliser le type de contenu lors de l'élaboration d'une demande spécifique pour un serveur spécifique afin d'éviter les frais généraux génériques.
Pacerier
2

Juste un petit conseil de mon côté pour télécharger des données d'image de toile HTML5:

Je travaille sur un projet d'imprimerie et j'ai eu quelques problèmes en raison du téléchargement d'images sur le serveur provenant d'un HTML5 canvas élément . J'ai eu du mal pendant au moins une heure et je n'ai pas réussi à enregistrer correctement l'image sur mon serveur.

Une fois que j'ai défini l' contentTypeoption de mon appel ajQuery jQuery, application/x-www-form-urlencodedtout s'est bien passé et les données encodées en base64 ont été interprétées correctement et enregistrées avec succès en tant qu'image.


Peut-être que cela aide quelqu'un!

Torsten Barthel
la source
4
Quel type de contenu l'envoyait-il avant de le modifier? Ce problème pourrait être dû au fait que le serveur ne prend pas en charge le type de contenu sous lequel vous l'envoyiez.
catorda
1

Si vous devez utiliser Content-Type = x-www-urlencoded-form, NE PAS utiliser FormDataCollection comme paramètre: Dans asp.net Core 2+ FormDataCollection n'a pas de constructeurs par défaut requis par Formatters. Utilisez IFormCollection à la place:

 public IActionResult Search([FromForm]IFormCollection type)
    {
        return Ok();
    }
jahansha
la source