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


1336

In HTTP ci sono due modi per pubblicare i dati: application/x-www-form-urlencodede multipart/form-data. Comprendo che la maggior parte dei browser è in grado di caricare file solo se multipart/form-datautilizzato. Esistono ulteriori indicazioni su quando utilizzare uno dei tipi di codifica in un contesto API (nessun browser interessato)? Questo potrebbe ad esempio basarsi su:

  • dimensione dei dati
  • esistenza di caratteri non ASCII
  • esistenza su dati binari (non codificati)
  • la necessità di trasferire dati aggiuntivi (come il nome del file)

Fondamentalmente non ho trovato alcuna guida formale sul web per quanto riguarda l'utilizzo dei diversi tipi di contenuto finora.


75
Va detto che questi sono i due tipi MIME utilizzati dai moduli HTML. Lo stesso HTTP non ha questa limitazione ... si può usare qualunque tipo di MIME desideri tramite HTTP.
tybro0103,

Risposte:


2015

TL; DR

Sommario; se si dispone di dati binari (non alfanumerici) (o di un payload di dimensioni significative) da trasmettere, utilizzare multipart/form-data. Altrimenti, usa application/x-www-form-urlencoded.


I tipi MIME che menzioni sono le due Content-Typeintestazioni per le richieste HTTP POST che gli user-agent (browser) devono supportare. Lo scopo di entrambi questi tipi di richieste è di inviare un elenco di coppie nome / valore al server. A seconda del tipo e della quantità di dati trasmessi, uno dei metodi sarà più efficiente dell'altro. Per capire perché, devi guardare a ciò che ognuno sta facendo sotto le coperte.

Infatti application/x-www-form-urlencoded, il corpo del messaggio HTTP inviato al server è essenzialmente una stringa di query gigante: le coppie nome / valore sono separate dalla e commerciale ( &) e i nomi sono separati dai valori dal simbolo uguale ( =). Un esempio di questo sarebbe: 

MyVariableOne=ValueOne&MyVariableTwo=ValueTwo

Secondo le specifiche :

I caratteri [riservati e] non alfanumerici sono sostituiti da `% HH ', un segno di percentuale e due cifre esadecimali che rappresentano il codice ASCII del carattere

Ciò significa che per ogni byte non alfanumerico che esiste in uno dei nostri valori, occorreranno tre byte per rappresentarlo. Per file binari di grandi dimensioni, triplicare il payload sarà altamente inefficiente.

Ecco dove multipart/form-dataentra in gioco. Con questo metodo di trasmissione di coppie nome / valore, ogni coppia viene rappresentata come "parte" in un messaggio MIME (come descritto da altre risposte). Le parti sono separate da un particolare limite di stringa (scelto specificamente in modo tale che questa stringa di limite non si verifichi in nessuno dei payload di "valore"). Ogni parte ha il suo set di intestazioni MIME come Content-Typee Content-Disposition, in particolare , che possono dare ad ogni parte il suo "nome". Il valore di ciascuna coppia nome / valore è il payload di ciascuna parte del messaggio MIME. Le specifiche MIME ci offrono più opzioni quando si rappresenta il valore del payload: possiamo scegliere una codifica più efficiente dei dati binari per risparmiare larghezza di banda (ad esempio base 64 o binario grezzo).

Perché non usare multipart/form-datatutto il tempo? Per valori alfanumerici brevi (come la maggior parte dei moduli Web), il sovraccarico di aggiungere tutte le intestazioni MIME supererà in modo significativo qualsiasi risparmio derivante da una codifica binaria più efficiente.


84
X-www-form-urlencoded ha un limite di lunghezza o è illimitato?
Pacerier

35
@Pacerier Il limite viene applicato dal server che riceve la richiesta POST. Vedi questa discussione per ulteriori discussioni: stackoverflow.com/questions/2364840/…
Matt Bridges

5
@ZiggyTheHamster JSON e BSON sono entrambi più efficienti per diversi tipi di dati. Base64 è inferiore a gzip, per entrambi i metodi di serializzazione. Base64 non porta alcun vantaggio, HTTP supporta i pyload binari.
Tiberiu-Ionuț Stan

16
Si noti inoltre che se un modulo contiene un caricamento di file denominato, l'unica scelta sono i dati del modulo, poiché urlencoded non ha un modo per posizionare il nome del file (nei dati del modulo è il parametro del nome per la disposizione del contenuto).
Guido van Rossum,

4
@EML see my parententhenth "(scelto specificatamente in modo che questa stringa limite non si presenti in nessuno dei" payload "di valore)"
Matt Bridges

151

LEGGI QUI AL PRIMO PARA QUI!

So che sono 3 anni troppo tardi, ma la risposta (accettata) di Matt è incompleta e alla fine ti metterà nei guai. La chiave qui è che, se si sceglie di utilizzare multipart/form-data, il limite non deve apparire nei dati del file che il server alla fine riceve.

Questo non è un problema application/x-www-form-urlencoded, perché non ci sono confini. x-www-form-urlencodedpuò anche gestire sempre dati binari, con la semplice opportunità di trasformare un byte arbitrario in tre 7BITbyte. Inefficiente, ma funziona (e nota che il commento sulla non possibilità di inviare nomi di file e dati binari non è corretto; devi semplicemente inviarlo come un'altra coppia chiave / valore).

Il problema multipart/form-dataè che il separatore di confine non deve essere presente nei dati del file (vedere RFC 2388 ; la sezione 5.2 include anche una scusa piuttosto scadente per non avere un tipo MIME aggregato adeguato che eviti questo problema).

Quindi, a prima vista, multipart/form-datanon ha alcun valore in nessun caricamento di file, binario o altro. Se non si sceglie correttamente il limite, alla fine si verificherà un problema, indipendentemente dal fatto che si invii testo semplice o binario non elaborato: il server troverà un limite nel posto sbagliato e il file verrà troncato o il POST avrà esito negativo.

La chiave è scegliere una codifica e un limite in modo tale che i caratteri di confine selezionati non possano apparire nell'output codificato. Una soluzione semplice è quella di utilizzare base64( non utilizzare binari grezzi). In base64 3 byte arbitrari sono codificati in quattro caratteri a 7 bit, in cui il set di caratteri di output è [A-Za-z0-9+/=](ovvero caratteri alfanumerici, '+', '/' o '='). =è un caso speciale e può apparire solo alla fine dell'output codificato, come singolo =o doppio ==. Ora, scegli il confine come una stringa ASCII a 7 bit che non può apparire base64nell'output. Molte scelte che vedi in rete non superano questo test: MDN forma documenti, ad esempio, utilizzare "BLOB" come limite quando si inviano dati binari - non va bene. Tuttavia, qualcosa come "! Blob!" non apparirà mai in base64output.


52
Mentre una considerazione dei dati multipart / form è la garanzia che il confine non compaia nei dati, ciò è abbastanza semplice da realizzare scegliendo un confine sufficientemente lungo. Si prega di non utilizzare la codifica Base64 per ottenere questo risultato. Un limite generato casualmente e della stessa lunghezza di un UUID dovrebbe essere sufficiente: stackoverflow.com/questions/1705008/… .
Joshcodes,

20
@EML, Questo non ha affatto senso. Ovviamente il confine viene scelto automaticamente dal client http (browser) e il client sarà abbastanza intelligente da non usare un confine che si scontra con il contenuto dei file caricati. È semplice una partita di sottostringa index === -1.
Pacerier,

13
@Pacerier: (A) leggi la domanda: "nessun browser coinvolto, contesto API". (B) i browser non creano comunque richieste per te. Lo fai da solo, manualmente. Non c'è magia nei browser.
EML

12
@BeniBela, probabilmente suggerirà di usarlo '()+-./:=allora. Eppure generazione casuale con sottostringa di controllo è ancora la strada da percorrere e può essere fatto con una sola riga: while(true){r = rand(); if(data.indexOf(r) === -1){doStuff();break;}}. Il suggerimento di EML (convertito in base64 solo per evitare la corrispondenza di sottostringhe) è semplicemente strano, per non parlare del fatto che viene fornito con un degrado delle prestazioni non necessario. E tutti i guai per nulla dato che l'algoritmo a una riga è altrettanto chiaro e semplice. Base64 non è pensato per essere (ab) utilizzato in questo modo, poiché il corpo HTTP accetta tutti gli ottetti a 8 bit .
Pacerier,

31
Questa risposta non solo non aggiunge nulla alla discussione, ma dà anche consigli sbagliati. In primo luogo, ogni volta che si trasmettono dati casuali in parti separate, è sempre possibile che il limite scelto sia presente nel payload. L'unico modo per assicurarsi che ciò non accada è esaminare l'intero payload per ogni limite che ci viene in mente. Completamente poco pratico. Accettiamo solo la probabilità infinitesimale di una collisione e creiamo un limite ragionevole, come "--- confine- <UUID qui>-confine ---". In secondo luogo, l'utilizzo sempre di Base64 sprecherà la larghezza di banda e riempirà i buffer senza motivo.
vagelis

92

Non credo che HTTP sia limitato al POST in multipart o x-www-form-urlencoded. L' intestazione Content-Type è ortogonale al metodo POST HTTP (è possibile compilare il tipo MIME adatto a te). Questo è anche il caso delle webapp tipiche basate sulla rappresentazione HTML (ad esempio il payload json è diventato molto popolare per la trasmissione del payload per le richieste Ajax).

Per quanto riguarda API Restful su HTTP, i tipi di contenuto più popolari con cui sono entrato in contatto sono application / xml e application / json.

application / xml:

  • dimensione dei dati: XML molto dettagliato, ma di solito non è un problema quando si utilizza la compressione e si pensa che il caso di accesso in scrittura (ad esempio tramite POST o PUT) sia molto più raro dell'accesso in lettura (in molti casi è <3% di tutto il traffico ). Raramente lì dove ho dovuto ottimizzare le prestazioni di scrittura
  • esistenza di caratteri non ascii: puoi usare utf-8 come codifica in XML
  • esistenza di dati binari: sarebbe necessario utilizzare la codifica base64
  • dati del nome file: è possibile incapsulare questo campo interno in XML

application / json

  • dimensione dei dati: più compatto meno di XML, testo fermo, ma è possibile comprimere
  • caratteri non ascii: json è utf-8
  • dati binari: base64 (vedi anche json-binary-question )
  • nome file: incapsulare come propria sezione di campo all'interno di json

dati binari come risorsa propria

Vorrei provare a rappresentare i dati binari come risorsa / risorsa propria. Aggiunge un'altra chiamata ma disaccoppia meglio le cose. Immagini di esempio:

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

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

Nelle risorse successive potresti semplicemente incorporare la risorsa binaria come link:

<main-resource>
 ...
 <link href="http://imageserver.org/../foo.jpg"/>
</main-resource>

Interessante. Ma quando usare application / x-www-form-urlencoded e quando multipart / form-data?
massimo

3
application / x-www-form-urlencoded è il tipo mime predefinito della tua richiesta (vedi anche w3.org/TR/html401/interact/forms.html#h-17.13.4 ). Lo uso per i "normali" moduli web. Per API utilizzo application / xml | json. multipart / form-data è una campana nel pensare agli attaccamenti (all'interno del corpo della risposta diverse sezioni di dati sono concattenate con una stringa di confine definita).
manuel aldana,

4
Penso che l'OP probabilmente chiedesse solo i due tipi utilizzati dai moduli HTML, ma sono contento che sia stato sottolineato.
tybro0103,

30

Sono d'accordo con molte cose che ha detto Manuel. In effetti, i suoi commenti si riferiscono a questo URL ...

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

... quali Stati:

Il tipo di contenuto "application / x-www-form-urlencoded" non è efficace per l'invio di grandi quantità di dati binari o testo contenente caratteri non ASCII. Il tipo di contenuto "multipart / form-data" deve essere utilizzato per l'invio di moduli che contengono file, dati non ASCII e dati binari.

Tuttavia, per me si tratterebbe del supporto di strumenti / framework.

  • Con quali strumenti e framework ti aspetti che i tuoi utenti API costruiscano le loro app?
  • Hanno framework o componenti che possono usare per favorire un metodo rispetto all'altro?

Se hai un'idea chiara dei tuoi utenti e di come utilizzeranno la tua API, questo ti aiuterà a decidere. Se rendi difficile il caricamento di file per i tuoi utenti API, questi si allontaneranno, e trascorrerai molto tempo a supportarli.

Secondario a questo sarebbe lo strumento di supporto che hai per scrivere la tua API e quanto sia facile per te ospitare un meccanismo di caricamento sull'altro.


1
Salve, significa che ogni volta che pubblichiamo qualcosa sul web server, dobbiamo menzionare qual è il tipo di contenuto per far sapere al web server che dovrebbe decodificare i dati? Anche noi elaboriamo noi stessi la richiesta http, DEVE menzionare il tipo di contenuto giusto?
GMsoF,

2
@GMsoF, è facoltativo. Vedi stackoverflow.com/a/16693884/632951 . È possibile evitare di utilizzare il tipo di contenuto durante l'elaborazione di una richiesta specifica per un server specifico per evitare spese generali generiche.
Pacerier,

2

Solo un piccolo suggerimento da parte mia per il caricamento di dati immagine tela HTML5:

Sto lavorando a un progetto per una tipografia e ho avuto alcuni problemi a causa del caricamento di immagini sul server che provenivano da un canvaselemento HTML5 . Ho lottato per almeno un'ora e non l'ho trovato per salvare l'immagine correttamente sul mio server.

Una volta impostato l' contentTypeopzione della mia chiamata ajax jQuery su application/x-www-form-urlencodedtutto è andato per il verso giusto e i dati codificati in base64 sono stati interpretati correttamente e salvati correttamente come immagine.


Forse questo aiuta qualcuno!


4
Quale tipo di contenuto è stato inviato prima di modificarlo? Questo problema potrebbe essere dovuto al fatto che il server non supporta il tipo di contenuto che lo si stava inviando.
catorda,

1

Se è necessario utilizzare Content-Type = x-www-urlencoded-form, NON utilizzare FormDataCollection come parametro: In asp.net Core 2+ FormDataCollection non ha costruttori predefiniti richiesti da Formatters. Utilizzare invece IFormCollection:

 public IActionResult Search([FromForm]IFormCollection type)
    {
        return Ok();
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.