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

1336

En HTTP hay dos formas de enviar datos: application/x-www-form-urlencodedy multipart/form-data. Entiendo que la mayoría de los navegadores solo pueden cargar archivos si multipart/form-datase usan. ¿Hay alguna guía adicional sobre cuándo usar uno de los tipos de codificación en un contexto API (sin navegador involucrado)? Esto podría, por ejemplo, basarse en:

  • tamaño de datos
  • existencia de caracteres no ASCII
  • existencia en datos binarios (sin codificar)
  • la necesidad de transferir datos adicionales (como nombre de archivo)

Básicamente no encontré ninguna guía formal en la web con respecto al uso de los diferentes tipos de contenido hasta ahora.

max
fuente
75
Cabe mencionar que estos son los dos tipos MIME que utilizan los formularios HTML. HTTP en sí no tiene tal limitación ... uno puede usar cualquier tipo MIME que quiera a través de HTTP.
tybro0103

Respuestas:

2015

TL; DR

Resumen; si tiene datos binarios (no alfanuméricos) (o una carga útil de tamaño significativo) para transmitir, utilícelos multipart/form-data. De lo contrario, use application/x-www-form-urlencoded.


Los tipos MIME que menciona son los dos Content-Typeencabezados para las solicitudes HTTP POST que los agentes de usuario (navegadores) deben admitir. El propósito de ambos tipos de solicitudes es enviar una lista de pares de nombre / valor al servidor. Dependiendo del tipo y la cantidad de datos que se transmiten, uno de los métodos será más eficiente que el otro. Para entender por qué, debes mirar lo que cada uno está haciendo debajo de las sábanas.

Porque application/x-www-form-urlencoded, el cuerpo del mensaje HTTP enviado al servidor es esencialmente una cadena de consulta gigante: los pares de nombre / valor están separados por el signo y ( &), y los nombres están separados de los valores por el símbolo de igual ( =). Un ejemplo de esto sería: 

MyVariableOne=ValueOne&MyVariableTwo=ValueTwo

De acuerdo con la especificación :

Los caracteres [reservados y] no alfanuméricos se reemplazan por '% HH', un signo de porcentaje y dos dígitos hexadecimales que representan el código ASCII del carácter

Eso significa que por cada byte no alfanumérico que existe en uno de nuestros valores, se necesitarán tres bytes para representarlo. Para archivos binarios grandes, triplicar la carga útil será muy ineficiente.

Ahí es donde multipart/form-dataentra. Con este método de transmitir pares de nombre / valor, cada par se representa como una "parte" en un mensaje MIME (como se describe en otras respuestas). Las partes están separadas por un límite de cadena particular (elegido específicamente para que esta cadena de límite no aparezca en ninguna de las cargas útiles de "valor"). Cada parte tiene su propio conjunto de encabezados MIME como Content-Type, y en particular Content-Disposition, que puede dar a cada parte su "nombre". La pieza de valor de cada par nombre / valor es la carga útil de cada parte del mensaje MIME. La especificación MIME nos brinda más opciones al representar la carga útil del valor: podemos elegir una codificación más eficiente de datos binarios para ahorrar ancho de banda (por ejemplo, base 64 o incluso binario sin formato).

¿Por qué no usar multipart/form-datatodo el tiempo? Para valores alfanuméricos cortos (como la mayoría de los formularios web), la sobrecarga de agregar todos los encabezados MIME va a superar significativamente cualquier ahorro de una codificación binaria más eficiente.

Matt Bridges
fuente
84
¿Tiene x-www-form-urlencoded un límite de longitud o es ilimitado?
Pacerier
35
@Pacerier El límite es impuesto por el servidor que recibe la solicitud POST. Vea este hilo para más discusión: stackoverflow.com/questions/2364840/…
Matt Bridges
55
@ZiggyTheHamster JSON y BSON son cada vez más eficientes para diferentes tipos de datos. Base64 es inferior a gzip, para ambos métodos de serialización. Base64 no ofrece ninguna ventaja, HTTP admite pyloads binarias.
Tiberiu-Ionuț Stan
16
También tenga en cuenta que si un formulario contiene una carga de archivo con nombre, su única opción son los datos de formulario, porque urlencoded no tiene una forma de colocar el nombre de archivo (en datos de formulario es el parámetro de nombre para la disposición de contenido).
Guido van Rossum
44
@EML ve mi paréntesis "(elegido específicamente para que esta cadena de límite no aparezca en ninguna de las cargas útiles de" valor ")"
Matt Bridges
152

¡LEA AL MENOS EL PRIMERO PARA AQUÍ!

Sé que esto es 3 años demasiado tarde, pero la respuesta de Matt (aceptada) es incompleta y eventualmente te meterá en problemas. La clave aquí es que, si elige usar multipart/form-data, el límite no debe aparecer en los datos del archivo que el servidor finalmente recibe.

Esto no es un problema application/x-www-form-urlencoded, porque no hay límite. x-www-form-urlencodedTambién siempre puede manejar datos binarios, por el simple recurso de convertir un byte arbitrario en tres 7BITbytes. Ineficiente, pero funciona (y tenga en cuenta que el comentario sobre no poder enviar nombres de archivos y datos binarios es incorrecto; simplemente lo envía como otro par clave / valor).

El problema con multipart/form-dataes que el separador de límites no debe estar presente en los datos del archivo (consulte RFC 2388 ; la sección 5.2 también incluye una excusa poco convincente para no tener un tipo MIME agregado adecuado que evite este problema).

Entonces, a primera vista, no multipart/form-datatiene ningún valor en la carga de ningún archivo, binario o de otro tipo. Si no elige su límite correctamente, entonces usted va a llegar a tener un problema, si va a enviar texto o binario - el servidor se encuentra un límite en el lugar equivocado, y el archivo se truncará, o el POST fallará.

La clave es elegir una codificación y un límite de modo que los caracteres de límite seleccionados no puedan aparecer en la salida codificada. Una solución simple es usar base64( no usar binario sin formato). En base64, 3 bytes arbitrarios se codifican en cuatro caracteres de 7 bits, donde el conjunto de caracteres de salida es [A-Za-z0-9+/=]( es decir, alfanuméricos, '+', '/' o '='). =es un caso especial y solo puede aparecer al final de la salida codificada, como simple =o doble ==. Ahora, elija su límite como una cadena ASCII de 7 bits que no puede aparecer en la base64salida. Muchas de las opciones que ve en la red no pasan esta prueba: el MDN forma documentos, por ejemplo, use "blob" como límite cuando envíe datos binarios, lo cual no es bueno. Sin embargo, algo como "! Blob!" nunca aparecerá en la base64salida.

EML
fuente
52
Si bien la consideración de datos multiparte / formulario es garantizar que el límite no aparezca en los datos, esto es bastante simple de lograr eligiendo un límite que sea suficientemente largo. No utilice la codificación base64 para lograr esto. Un límite generado aleatoriamente y la misma longitud que un UUID debería ser suficiente: stackoverflow.com/questions/1705008/… .
Joshcodes
20
@EML, esto no tiene ningún sentido. Obviamente, el límite es elegido automáticamente por el cliente http (navegador) y el cliente será lo suficientemente inteligente como para no usar un límite que entre en conflicto con el contenido de sus archivos cargados. Es tan simple como una coincidencia de subcadena index === -1.
Pacerier
13
@Pacerier: (A) lea la pregunta: "sin navegador involucrado, contexto API". (B) los navegadores no crean solicitudes para usted de todos modos. Lo haces tú mismo, manualmente. No hay magia en los navegadores.
EML
12
@BeniBela, probablemente va a sugerir usarlo '()+-./:=entonces. Sin embargo, la generación aleatoria con subcadena cheque sigue siendo el camino a seguir y que se puede hacer con una sola línea: while(true){r = rand(); if(data.indexOf(r) === -1){doStuff();break;}}. La sugerencia de EML (convertir a base64 solo para evitar subcadenas coincidentes) es simplemente extraña, sin mencionar que viene con una degradación innecesaria del rendimiento. Y todo el problema por nada ya que el algoritmo de una línea es igualmente sencillo y simple. No se pretende que Base64 se use (ab) de esta manera, ya que el cuerpo HTTP acepta todos los octetos de 8 bits .
Pacerier
31
Esta respuesta no solo no agrega nada a la discusión, sino que también da consejos incorrectos. En primer lugar, siempre que se transmiten datos aleatorios en partes separadas, siempre es posible que el límite elegido esté presente en la carga útil. La ÚNICA forma de asegurarse de que esto no suceda es examinar toda la carga útil para cada límite que se nos ocurra. Completamente impracticable. Solo aceptamos la probabilidad infinitesimal de una colisión y obtenemos un límite razonable, como "--- límite- <UUID aquí> -límite ---". En segundo lugar, usar siempre Base64 desperdiciará el ancho de banda y llenará las memorias intermedias sin ningún motivo.
vagelis
92

No creo que HTTP esté limitado a POST en multiparte o x-www-form-urlencoded. El Encabezado de tipo de contenido es ortogonal al método HTTP POST (puede completar el tipo MIME que más le convenga). Este es también el caso de las aplicaciones web típicas basadas en representación HTML (por ejemplo, la carga útil json se hizo muy popular para transmitir la carga útil para las solicitudes ajax).

Con respecto a Restful API sobre HTTP, los tipos de contenido más populares con los que contacté son application / xml y application / json.

aplicación / xml:

  • tamaño de datos: XML muy detallado, pero generalmente no es un problema cuando se utiliza la compresión y se piensa que el caso de acceso de escritura (por ejemplo, a través de POST o PUT) es mucho más raro que el acceso de lectura (en muchos casos es <3% de todo el tráfico ) Rara vez hubo casos en los que tuve que optimizar el rendimiento de escritura
  • existencia de caracteres no ascii: puede usar utf-8 como codificación en XML
  • existencia de datos binarios: necesitaría usar codificación base64
  • datos de nombre de archivo: puede encapsular este campo interno en XML

aplicación / json

  • tamaño de datos: más compacto menos que XML, todavía texto, pero puede comprimir
  • caracteres no ascii: json es utf-8
  • datos binarios: base64 (ver también json-binary-question )
  • nombre de archivo de datos: encapsular como propia sección de campo dentro de json

datos binarios como recurso propio

Intentaría representar los datos binarios como un activo / recurso propio. Agrega otra llamada pero desacopla mejor las cosas. Imágenes de ejemplo:

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

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

En recursos posteriores, podría simplemente insertar el recurso binario como enlace:

<main-resource>
 ...
 <link href="http://imageserver.org/../foo.jpg"/>
</main-resource>
manuel aldana
fuente
Interesante. ¿Pero cuándo usar application / x-www-form-urlencoded y cuándo multipart / form-data?
max
3
application / x-www-form-urlencoded es el tipo mime predeterminado de su solicitud (consulte también w3.org/TR/html401/interact/forms.html#h-17.13.4 ). Lo uso para formularios web "normales". Para API utilizo application / xml | json. multipart / form-data es una campana al pensar en los archivos adjuntos (dentro del cuerpo de respuesta, varias secciones de datos se concatenan con una cadena de límite definida).
manuel aldana
44
Creo que el OP probablemente solo estaba preguntando sobre los dos tipos que usan los formularios HTML, pero me alegro de que esto se haya señalado.
tybro0103
30

Estoy de acuerdo con mucho de lo que ha dicho Manuel. De hecho, sus comentarios se refieren a esta url ...

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

... Que estados:

El tipo de contenido "application / x-www-form-urlencoded" es ineficiente para enviar grandes cantidades de datos binarios o texto que contenga caracteres no ASCII. El tipo de contenido "multipart / form-data" debe usarse para enviar formularios que contienen archivos, datos no ASCII y datos binarios.

Sin embargo, para mí todo se reduciría al soporte de herramientas / marcos.

  • ¿Con qué herramientas y marcos espera que sus usuarios de API construyan sus aplicaciones?
  • ¿Tienen marcos o componentes que pueden usar que favorecen un método sobre el otro?

Si tiene una idea clara de sus usuarios y cómo utilizarán su API, eso lo ayudará a decidir. Si dificulta la carga de archivos para los usuarios de su API, se alejarán, de lo que pasará mucho tiempo dándoles soporte.

Secundario a esto sería el soporte de herramientas que USTED tiene para escribir su API y lo fácil que es para usted acomodar un mecanismo de carga sobre el otro.

Martin Peck
fuente
1
Hola, ¿significa que cada vez que publicamos algo en el servidor web, tenemos que mencionar cuál es el tipo de contenido para que el servidor web sepa si debe decodificar los datos? Incluso nosotros mismos elaboramos la solicitud http, DEBEMOS mencionar el tipo de contenido, ¿verdad?
GMsoF
2
@GMsoF, es opcional. Ver stackoverflow.com/a/16693884/632951 . Es posible que desee evitar el uso de tipo de contenido al elaborar una solicitud específica para un servidor específico para evitar gastos generales genéricos.
Pacerier
2

Solo una pequeña pista de mi parte para cargar datos de imágenes de lienzo HTML5:

Estoy trabajando en un proyecto para una imprenta y tuve algunos problemas debido a la carga de imágenes en el servidor que provenían de un canvaselemento HTML5 . Estuve luchando durante al menos una hora y no pude guardar la imagen correctamente en mi servidor.

Una vez que configuré la contentTypeopción de mi llamada jQuery ajax, application/x-www-form-urlencodedtodo salió bien y los datos codificados en base64 se interpretaron correctamente y se guardaron con éxito como una imagen.


¡Quizás eso ayude a alguien!

Torsten Barthel
fuente
44
¿Qué tipo de contenido lo estaba enviando antes de cambiarlo? Este problema podría deberse a que el servidor no admite el tipo de contenido con el que lo estaba enviando.
catorda
1

Si necesita usar Content-Type = x-www-urlencoded-form, entonces NO use FormDataCollection como parámetro: en asp.net Core 2+ FormDataCollection no tiene constructores predeterminados que requieren los formateadores. Utilice IFormCollection en su lugar:

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