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

1336

No HTTP, existem duas maneiras de colocar dados no POST: application/x-www-form-urlencodede multipart/form-data. Entendo que a maioria dos navegadores só pode fazer upload de arquivos se multipart/form-datafor usada. Existe alguma orientação adicional quando usar um dos tipos de codificação em um contexto de API (nenhum navegador envolvido)? Por exemplo, isso pode ser baseado em:

  • tamanho dos dados
  • existência de caracteres não ASCII
  • existência em dados binários (não codificados)
  • a necessidade de transferir dados adicionais (como nome do arquivo)

Basicamente, não encontrei nenhuma orientação formal na Web sobre o uso dos diferentes tipos de conteúdo até o momento.

max
fonte
75
Deve-se mencionar que esses são os dois tipos MIME usados ​​pelos formulários HTML. O próprio HTTP não tem essa limitação ... pode-se usar o tipo MIME que ele quiser via HTTP.
tybro0103

Respostas:

2014

TL; DR

Resumo; se você tiver dados binários (não alfanuméricos) (ou uma carga útil de tamanho significativo) para transmitir, use multipart/form-data. Caso contrário, use application/x-www-form-urlencoded.


Os tipos MIME mencionados são os dois Content-Typecabeçalhos das solicitações HTTP POST que os agentes do usuário (navegadores) devem suportar. O objetivo de ambos os tipos de solicitações é enviar uma lista de pares de nome / valor ao servidor. Dependendo do tipo e quantidade de dados transmitidos, um dos métodos será mais eficiente que o outro. Para entender o porquê, você deve examinar o que cada um está fazendo escondido.

Pois application/x-www-form-urlencoded, o corpo da mensagem HTTP enviada ao servidor é essencialmente uma string de consulta gigante - os pares nome / valor são separados pelo e comercial ( &) e os nomes são separados dos valores pelo símbolo de igual ( =). Um exemplo disso seria: 

MyVariableOne=ValueOne&MyVariableTwo=ValueTwo

De acordo com a especificação :

Os caracteres [reservados e] não alfanuméricos são substituídos por `% HH ', um sinal de porcentagem e dois dígitos hexadecimais que representam o código ASCII do caractere

Isso significa que, para cada byte não alfanumérico existente em um de nossos valores, serão necessários três bytes para representá-lo. Para arquivos binários grandes, triplicar a carga útil será altamente ineficiente.

É aí que multipart/form-dataentra. Com esse método de transmissão de pares nome / valor, cada par é representado como uma "parte" em uma mensagem MIME (conforme descrito por outras respostas). As peças são separadas por um limite de cadeia específico (escolhido especificamente para que essa cadeia de limite não ocorra em nenhuma das cargas úteis de "valor"). Cada parte tem seu próprio conjunto de cabeçalhos MIME Content-Type, como , particularmente Content-Disposition, o que pode dar a cada parte seu "nome". A parte do valor de cada par nome / valor é a carga útil de cada parte da mensagem MIME. A especificação MIME nos oferece mais opções ao representar a carga útil do valor - podemos escolher uma codificação mais eficiente dos dados binários para economizar largura de banda (por exemplo, base 64 ou até binário bruto).

Por que não usar multipart/form-datao tempo todo? Para valores alfanuméricos curtos (como a maioria dos formulários da Web), a sobrecarga de adicionar todos os cabeçalhos MIME superará significativamente qualquer economia com a codificação binária mais eficiente.

Matt Bridges
fonte
84
O código x-www-form-urlencode tem um limite de comprimento ou é ilimitado?
Pacerier 9/03/13
35
@Pacerier O limite é imposto pelo servidor que recebe a solicitação POST. Veja este tópico para mais discussão: stackoverflow.com/questions/2364840/...
Matt Bridges
5
@ZiggyTheHamster JSON e BSON são mais eficientes para diferentes tipos de dados. Base64 é inferior ao gzip, para ambos os métodos de serialização. O Base64 não traz nenhuma vantagem, o HTTP suporta pyloads binários.
Tiberiu-Ionuț Stan
16
Observe também que, se um formulário contém um upload de arquivo nomeado, sua única opção é form-data, porque o urlencoded não tem como colocar o nome do arquivo (em form-data é o parâmetro name para a disposição do conteúdo).
Guido van Rossum
4
@EML ver meu parênteses "(escolhido especificamente para que esta corda limite não ocorre em qualquer um dos 'valor' payloads)"
Matt Bridges
151

LEIA PELO MENOS O PRIMEIRO PARA AQUI!

Eu sei que isso é 3 anos tarde demais, mas a resposta (aceita) de Matt está incompleta e acabará por causar problemas. A chave aqui é que, se você optar por usar multipart/form-data, o limite não deverá aparecer nos dados do arquivo que o servidor eventualmente receber.

Isso não é um problema para application/x-www-form-urlencoded, porque não há limites. x-www-form-urlencodedtambém pode sempre manipular dados binários, pelo simples expediente de transformar um byte arbitrário em três 7BITbytes. Ineficiente, mas funciona (e observe que o comentário sobre a impossibilidade de enviar nomes de arquivos e dados binários está incorreto; basta enviá-lo como outro par de chave / valor).

O problema multipart/form-dataé que o separador de limites não deve estar presente nos dados do arquivo (consulte a RFC 2388 ; a seção 5.2 também inclui uma desculpa esfarrapada por não ter um tipo MIME agregado adequado que evite esse problema).

Portanto, à primeira vista, multipart/form-datanão tem valor algum em nenhum upload de arquivo, binário ou outro. Se você não escolher o seu limite corretamente, então você vai , eventualmente, ter um problema, se você está enviando texto simples ou binário simples - o servidor encontrará um limite no lugar errado, e seu arquivo será truncado, ou o POST vai falhar.

A chave é escolher uma codificação e um limite para que os caracteres de limite selecionados não possam aparecer na saída codificada. Uma solução simples é usar base64( não use binário bruto). Na base64, 3 bytes arbitrários são codificados em quatro caracteres de 7 bits, onde o conjunto de caracteres de saída é [A-Za-z0-9+/=](ou seja, alfanuméricos, '+', '/' ou '='). =é um caso especial e pode aparecer apenas no final da saída codificada, como um único =ou um duplo ==. Agora, escolha seu limite como uma sequência ASCII de 7 bits que não pode aparecer na base64saída. Muitas opções que você vê na rede falham neste teste - o MDN forma docs, por exemplo, use "blob" como limite ao enviar dados binários - nada bom. No entanto, algo como "! Blob!" nunca aparecerá na base64saída.

EML
fonte
52
Embora uma consideração de dados de várias partes / formulário seja a garantia de que o limite não apareça nos dados, isso é bastante simples de realizar, escolhendo um limite suficientemente longo. Por favor, não use a codificação base64 para fazer isso. Um limite gerado aleatoriamente e o mesmo comprimento que um UUID deve ser suficiente: stackoverflow.com/questions/1705008/… .
precisa saber é o seguinte
20
@ EML, isso não faz sentido. Obviamente, o limite é escolhido automaticamente pelo cliente http (navegador) e o cliente será inteligente o suficiente para não usar um limite conflitante com o conteúdo dos arquivos enviados. É tão simples quanto uma correspondência de substring index === -1.
Pacerier 11/11/14
13
@Pacerier: (A) leia a pergunta: "nenhum navegador envolvido, contexto da API". (B) navegadores não constroem solicitações para você de qualquer maneira. Você mesmo, manualmente. Não há mágica nos navegadores.
EML
12
@ BeniBela, ele provavelmente vai sugerir o uso '()+-./:=então. No entanto, a geração aleatória com substring cheque ainda é o caminho a percorrer e que pode ser feito com uma linha: while(true){r = rand(); if(data.indexOf(r) === -1){doStuff();break;}}. A sugestão da EML (converter para base64 apenas para evitar a correspondência de substrings) é simplesmente estranha, sem mencionar que ela vem com a degradação desnecessária do desempenho. E todo o problema por nada, uma vez que o algoritmo de uma linha é igualmente direto e simples. Base64 não deve ser (ab) usado dessa maneira, pois o corpo HTTP aceita todos os octetos de 8 bits .
Pacerier 27/07
31
Essa resposta não apenas não acrescenta nada à discussão, mas também fornece conselhos errados. Primeiramente, sempre que transmitir dados aleatórios em partes separadas, é sempre possível que o limite escolhido esteja presente na carga útil. A única maneira de garantir que isso não aconteça é examinar toda a carga útil para cada limite que surgirmos. Completamente impraticável. Nós apenas aceitamos a probabilidade infinitesimal de uma colisão e criamos um limite razoável, como "--- limite- <fronteira UUID aqui>-limite ---". Em segundo lugar, sempre o uso do Base64 desperdiçará largura de banda e preencherá os buffers sem nenhum motivo.
Vagelis 5/05
92

Eu não acho que o HTTP esteja limitado ao POST em multipart ou x-www-form-urlencoded. O cabeçalho do tipo de conteúdo é ortogonal ao método HTTP POST (você pode preencher o tipo MIME que mais lhe convier). Esse também é o caso de aplicativos da Web típicos baseados em representação HTML (por exemplo, a carga útil do json se tornou muito popular para transmitir carga útil para solicitações de ajax).

Em relação à API Restful over HTTP, os tipos de conteúdo mais populares com os quais eu entrei em contato são application / xml e application / json.

application / xml:

  • tamanho dos dados: XML muito detalhado, mas geralmente não é um problema ao usar a compactação e pensar que o caso de acesso de gravação (por exemplo, através de POST ou PUT) é muito mais raro como acesso de leitura (em muitos casos, é <3% de todo o tráfego ) Raramente existem casos em que eu tive que otimizar o desempenho de gravação
  • existência de caracteres não-ascii: você pode usar utf-8 como codificação em XML
  • existência de dados binários: precisaria usar a codificação base64
  • nome do arquivo: você pode encapsular esse campo interno em XML

application / json

  • tamanho dos dados: mais compacto menos que XML, texto estático, mas você pode compactar
  • caracteres não-ascii: json is utf-8
  • dados binários: base64 (consulte também json-binary-question )
  • nome do arquivo data: encapsule como seção de campo própria dentro de json

dados binários como recurso próprio

Eu tentaria representar dados binários como ativo / recurso próprio. Ele adiciona outra chamada, mas desacopla as coisas melhor. Imagens de exemplo:

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

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

Em recursos posteriores, você pode simplesmente incorporar o recurso binário como link:

<main-resource>
 ...
 <link href="http://imageserver.org/../foo.jpg"/>
</main-resource>
manuel aldana
fonte
Interessante. Mas quando usar application / x-www-form-urlencoded e quando multipart / form-data?
Max
3
application / x-www-form-urlencoded é o tipo mime padrão de sua solicitação (consulte também w3.org/TR/html401/interact/forms.html#h-17.13.4 ). Eu o uso para formulários da web "normais". Para API, uso application / xml | json. multipart / form-data é um sinal para pensar em anexos (dentro do corpo da resposta, várias seções de dados são concatenadas com uma cadeia de limites definida).
manuel aldana
4
Acho que o OP provavelmente estava apenas perguntando sobre os dois tipos que os formulários HTML usam, mas estou feliz que isso tenha sido apontado.
precisa saber é o seguinte
30

Eu concordo com muito do que Manuel disse. De fato, seus comentários se referem a este URL ...

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

... quais Estados:

O tipo de conteúdo "application / x-www-form-urlencoded" é ineficiente para enviar grandes quantidades de dados binários ou texto contendo caracteres não ASCII. O tipo de conteúdo "dados de várias partes / formulário" deve ser usado para enviar formulários que contêm arquivos, dados não ASCII e dados binários.

No entanto, para mim, isso se resumiria ao suporte de ferramentas / estruturas.

  • Com quais ferramentas e estruturas você espera que os usuários da API criem seus aplicativos?
  • Eles possuem estruturas ou componentes que podem usar que favorecem um método em relação ao outro?

Se você tiver uma idéia clara de seus usuários e como eles farão uso da sua API, isso ajudará você a decidir. Se você dificultar o upload de arquivos para os usuários da API, eles se afastam, e você gastará muito tempo apoiando-os.

Em segundo lugar, seria o suporte da ferramenta que você tem para escrever sua API e como é fácil acomodar um mecanismo de upload em detrimento do outro.

Martin Peck
fonte
1
Oi, isso significa que toda vez que postamos algo no servidor da web, precisamos mencionar qual é o tipo de conteúdo para informar ao servidor da web que ele deve decodificar os dados? Mesmo nós criamos o pedido http, nós DEVEMOS mencionar o tipo de conteúdo, certo?
GMsoF 17/07
2
@GMsoF, é opcional. Consulte stackoverflow.com/a/16693884/632951 . Convém evitar o uso do tipo de conteúdo ao criar uma solicitação específica para um servidor específico para evitar sobrecargas genéricas.
Pacerier 11/11/14
2

Apenas uma pequena dica do meu lado para fazer upload de dados de imagem em tela HTML5:

Estou trabalhando em um projeto para uma gráfica e tive alguns problemas devido ao upload de imagens para o servidor provenientes de um canvaselemento HTML5 . Eu estava lutando por pelo menos uma hora e não consegui salvar a imagem corretamente no meu servidor.

Depois de definir a contentTypeopção da minha chamada jQuery ajax, application/x-www-form-urlencodedtudo correu do jeito certo e os dados codificados em base64 foram interpretados corretamente e salvos com sucesso como uma imagem.


Talvez isso ajude alguém!

Torsten Barthel
fonte
4
Que tipo de conteúdo estava enviando antes de você alterá-lo? Esse problema pode ter ocorrido devido ao servidor não suportar o tipo de conteúdo para o qual você estava enviando.
catorda
1

Se você precisar usar Content-Type = x-www-urlencoded-form, NÃO use FormDataCollection como parâmetro: No asp.net Core 2+ FormDataCollection não tem construtores padrão, exigidos pelos Formadores. Use IFormCollection em vez disso:

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