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

1336

Di HTTP ada dua cara untuk POST data: application/x-www-form-urlencodeddan multipart/form-data. Saya mengerti bahwa sebagian besar browser hanya dapat mengunggah file jika multipart/form-datadigunakan. Apakah ada panduan tambahan kapan menggunakan salah satu jenis penyandian dalam konteks API (tidak ada browser yang terlibat)? Ini mungkin misalnya didasarkan pada:

  • ukuran data
  • keberadaan karakter non-ASCII
  • keberadaan pada data biner (tidak ter-enkripsi)
  • kebutuhan untuk mentransfer data tambahan (seperti nama file)

Saya pada dasarnya tidak menemukan panduan formal di web mengenai penggunaan tipe konten yang berbeda sejauh ini.

maks
sumber
75
Harus disebutkan bahwa ini adalah dua tipe MIME yang digunakan HTML. HTTP sendiri tidak memiliki batasan seperti itu ... orang dapat menggunakan tipe MIME apa pun yang diinginkannya melalui HTTP.
tybro0103

Jawaban:

2014

TL; DR

Ringkasan; jika Anda memiliki data biner (non-alfanumerik) (atau muatan berukuran signifikan) untuk dikirim, gunakan multipart/form-data. Kalau tidak, gunakan application/x-www-form-urlencoded.


Jenis MIME yang Anda sebutkan adalah dua Content-Typeheader untuk permintaan HTTP POST yang harus didukung oleh agen-pengguna (browser). Tujuan dari kedua jenis permintaan tersebut adalah untuk mengirim daftar pasangan nama / nilai ke server. Tergantung pada jenis dan jumlah data yang dikirim, salah satu metode akan lebih efisien daripada yang lain. Untuk memahami alasannya, Anda harus melihat apa yang dilakukan masing-masing di bawah selimut.

Sebab application/x-www-form-urlencoded, isi pesan HTTP yang dikirim ke server pada dasarnya adalah satu string kueri raksasa - pasangan nama / nilai dipisahkan oleh ampersand ( &), dan nama dipisahkan dari nilai dengan simbol sama dengan ( =). Contohnya adalah: 

MyVariableOne=ValueOne&MyVariableTwo=ValueTwo

Sesuai dengan spesifikasi :

[Cadangan dan] karakter non-alfanumerik digantikan oleh `% HH ', tanda persen dan dua digit heksadesimal yang mewakili kode ASCII karakter.

Itu berarti bahwa untuk setiap byte non-alfanumerik yang ada di salah satu nilai kami, dibutuhkan tiga byte untuk mewakilinya. Untuk file biner besar, tiga kali lipat payload akan menjadi sangat tidak efisien.

Di situlah multipart/form-datamasuk. Dengan metode ini mentransmisikan pasangan nama / nilai, setiap pasangan direpresentasikan sebagai "bagian" dalam pesan MIME (seperti yang dijelaskan oleh jawaban lain). Bagian-bagian dipisahkan oleh batas string tertentu (dipilih secara khusus sehingga string batas ini tidak terjadi pada muatan "nilai" mana pun). Setiap bagian memiliki seperangkat header MIME sendiri Content-Type, dan khususnya Content-Disposition, yang dapat memberi masing-masing bagian "nama". Sepotong nilai dari setiap pasangan nama / nilai adalah muatan dari setiap bagian dari pesan MIME. MIME spec memberi kita lebih banyak opsi saat mewakili nilai muatan - kita dapat memilih pengkodean data biner yang lebih efisien untuk menghemat bandwidth (mis. Basis 64 atau bahkan biner mentah).

Kenapa tidak digunakan multipart/form-datasepanjang waktu? Untuk nilai alfanumerik singkat (seperti kebanyakan formulir web), biaya tambahan untuk menambahkan semua header MIME akan secara signifikan lebih besar daripada penghematan dari pengkodean biner yang lebih efisien.

Matt Bridges
sumber
84
Apakah x-www-form-urlencoded memiliki batas panjang, atau tidak terbatas?
Pacerier
35
@Pacerier Batas diberlakukan oleh server yang menerima permintaan POST. Lihat utas ini untuk diskusi lebih lanjut: stackoverflow.com/questions/2364840/…
Matt Bridges
5
@ZiggyTheHamster JSON dan BSON masing-masing lebih efisien untuk berbagai jenis data. Base64 lebih rendah daripada gzip, untuk kedua metode serialisasi. Base64 tidak membawa keuntungan sama sekali, HTTP mendukung pyloads biner.
Tiberiu-Ionuț Stan
16
Juga perhatikan bahwa jika suatu form berisi upload file bernama, satu-satunya pilihan Anda adalah form-data, karena urlencoded tidak memiliki cara untuk menempatkan nama file (dalam form-data itu adalah parameter nama untuk disposisi-konten).
Guido van Rossum
4
@ EML lihat tanda kurung saya "(dipilih secara khusus agar string batas ini tidak terjadi pada muatan" nilai "mana pun)"
Matt Bridges
151

BACA SETIDAKNYA PARA PERTAMA DI SINI!

Saya tahu ini terlambat 3 tahun, tetapi jawaban Matt (diterima) tidak lengkap dan pada akhirnya akan membuat Anda kesulitan. Kuncinya di sini adalah, jika Anda memilih untuk menggunakan multipart/form-data, batas tidak boleh muncul dalam data file yang akhirnya diterima server.

Ini bukan masalah bagi application/x-www-form-urlencoded, karena tidak ada batasan. x-www-form-urlencodedjuga dapat selalu menangani data biner, dengan cara sederhana mengubah satu byte sewenang-wenang menjadi tiga 7BITbyte. Tidak efisien, tetapi berfungsi (dan perhatikan bahwa komentar tentang tidak dapat mengirim nama file serta data biner salah; Anda hanya mengirimnya sebagai pasangan kunci / nilai lain).

Masalahnya multipart/form-dataadalah bahwa pemisah batas tidak boleh ada dalam data file (lihat RFC 2388 ; bagian 5.2 juga mencakup alasan yang agak timpang karena tidak memiliki tipe MIME agregat yang tepat yang menghindari masalah ini).

Jadi, pada pandangan pertama, multipart/form-datatidak ada nilai sama sekali dalam file apa pun , biner atau lainnya. Jika Anda tidak memilih batas dengan benar, maka pada akhirnya Anda akan memiliki masalah, apakah Anda mengirim teks biasa atau biner mentah - server akan menemukan batas di tempat yang salah, dan file Anda akan terpotong, atau POST akan gagal.

Kuncinya adalah memilih pengkodean dan batas sehingga karakter batas yang Anda pilih tidak dapat muncul dalam output yang disandikan. Salah satu solusi sederhana adalah dengan menggunakan base64(jangan tidak menggunakan biner mentah). Dalam base64 3 byte acak dikodekan menjadi empat karakter 7-bit, di mana himpunan karakter keluarannya [A-Za-z0-9+/=](yaitu alfanumerik, '+', '/' atau '='). =adalah kasus khusus, dan hanya dapat muncul di akhir output yang disandikan, sebagai tunggal =atau ganda ==. Sekarang, pilih batas Anda sebagai string ASCII 7-bit yang tidak dapat muncul dalam base64output. Banyak pilihan yang Anda lihat di internet gagal dalam tes ini - MDN membentuk dokumen, misalnya, gunakan "gumpalan" sebagai batas saat mengirim data biner - tidak baik. Namun, sesuatu seperti "gumpalan!" tidak akan pernah muncul dalam base64output.

EML
sumber
52
Sementara pertimbangan multipart / formulir-data adalah memastikan batas tidak muncul dalam data, ini cukup sederhana untuk dicapai dengan memilih batas yang cukup panjang. Tolong jangan kami pengkodean base64 untuk mencapai ini. Batas yang dibuat secara acak dan panjang yang sama dengan UUID harus memadai: stackoverflow.com/questions/1705008/… .
Joshcodes
20
@ EML, Ini tidak masuk akal sama sekali. Jelas batas dipilih secara otomatis oleh klien http (browser) dan klien akan cukup pintar untuk tidak menggunakan batas yang berbenturan dengan konten file yang Anda unggah. Ini adalah pertandingan substring yang sederhana index === -1.
Pacerier
13
@Pacerier: (A) baca pertanyaan: "tidak ada browser yang terlibat, konteks API". (B) browser tidak membangun permintaan untuk Anda. Anda melakukannya sendiri, secara manual. Tidak ada keajaiban di browser.
EML
12
@ Beneni, Dia mungkin akan menyarankan untuk menggunakan '()+-./:=itu. Namun generasi acak dengan substring cek masih merupakan cara untuk pergi dan itu bisa dilakukan dengan satu baris: while(true){r = rand(); if(data.indexOf(r) === -1){doStuff();break;}}. Saran EML (konversi ke base64 hanya untuk menghindari substring yang cocok) benar-benar aneh, belum lagi disertai dengan penurunan kinerja yang tidak dibutuhkan. Dan semua masalah tidak ada karena algoritma satu baris sama mudah dan sederhana. Base64 tidak dimaksudkan untuk digunakan (ab) dengan cara ini, karena badan HTTP menerima semua oktet 8-bit .
Pacerier
31
Jawaban ini tidak hanya menambah apa pun pada diskusi, tetapi juga memberikan saran yang salah. Pertama, setiap kali mentransmisikan data acak dalam bagian-bagian yang terpisah, selalu dimungkinkan bahwa batas yang dipilih akan hadir dalam muatan. Cara HANYA untuk memastikan ini tidak terjadi adalah dengan memeriksa seluruh muatan untuk setiap batas yang kami buat. Sama sekali tidak praktis. Kami hanya menerima probabilitas sangat kecil dari tabrakan dan muncul dengan batas yang masuk akal, seperti "--- batas- <UUID sini> -batas ---". Kedua, selalu menggunakan Base64 akan menghabiskan bandwidth dan mengisi buffer tanpa alasan sama sekali.
vagelis
92

Saya tidak berpikir HTTP terbatas pada POST di multi-bagian atau x-www-form-urlencoded. The Content-Type header adalah orthogonal dengan metode HTTP POST (Anda dapat mengisi tipe MIME yang cocok untuk anda). Ini juga merupakan kasus untuk webapps berbasis representasi HTML (mis. Json payload menjadi sangat populer untuk mentransmisikan payload untuk permintaan ajax).

Mengenai Restful API over HTTP, tipe konten paling populer yang saya hubungi adalah application / xml dan application / json.

aplikasi / xml:

  • data-size: XML sangat verbose, tetapi biasanya tidak menjadi masalah ketika menggunakan kompresi dan berpikir bahwa case akses tulis (misalnya melalui POST atau PUT) jauh lebih jarang daripada akses baca (dalam banyak kasus itu adalah <3% dari semua lalu lintas ). Jarang ada kasus di mana saya harus mengoptimalkan kinerja menulis
  • keberadaan karakter non-ascii: Anda dapat menggunakan utf-8 sebagai penyandian dalam XML
  • keberadaan data biner: perlu menggunakan pengkodean base64
  • nama file data: Anda dapat merangkum bidang dalam ini dalam XML

aplikasi / json

  • data-size: lebih kecil daripada XML, masih teks, tetapi Anda bisa kompres
  • karakter non-ascii: json adalah utf-8
  • data biner: base64 (juga lihat json-binary-question )
  • nama file data: merangkum sebagai bagian-bidang sendiri di dalam json

data biner sebagai sumber daya sendiri

Saya akan mencoba untuk mewakili data biner sebagai aset / sumber daya sendiri. Itu menambah panggilan lain tetapi memisahkan hal-hal yang lebih baik. Contoh gambar:

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

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

Di sumber daya yang lebih baru Anda bisa dengan mudah menyejajarkan sumber daya biner sebagai tautan:

<main-resource>
 ...
 <link href="http://imageserver.org/../foo.jpg"/>
</main-resource>
manuel aldana
sumber
Menarik. Tetapi kapan harus menggunakan aplikasi / x-www-form-urlencoded dan kapan multipart / form-data?
Maks
3
application / x-www-form-urlencoded adalah tipe mime default dari permintaan Anda (lihat juga w3.org/TR/html401/interact/forms.html#h-17.13.4 ). Saya menggunakannya untuk formulir web "normal". Untuk API saya menggunakan aplikasi / xml | json. multipart / form-data adalah lonceng dalam memikirkan lampiran (di dalam tubuh respons beberapa bagian-data dienkripsi dengan string batas yang ditentukan).
manuel aldana
4
Saya pikir OP mungkin hanya bertanya tentang dua jenis yang menggunakan formulir HTML, tapi saya senang ini ditunjukkan.
tybro0103
30

Saya setuju dengan banyak hal yang dikatakan Manuel. Bahkan, komentarnya merujuk ke url ini ...

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

... yang menyatakan:

Jenis konten "application / x-www-form-urlencoded" tidak efisien untuk mengirim sejumlah besar data biner atau teks yang mengandung karakter non-ASCII. Jenis konten "multipart / formulir-data" harus digunakan untuk mengirimkan formulir yang berisi file, data non-ASCII, dan data biner.

Namun, bagi saya itu akan turun ke dukungan alat / kerangka kerja.

  • Apa alat dan kerangka kerja yang Anda harapkan dari pengguna API untuk membangun aplikasi mereka?
  • Apakah mereka memiliki kerangka kerja atau komponen yang dapat mereka gunakan yang mendukung satu metode daripada yang lain?

Jika Anda mendapatkan gagasan yang jelas tentang pengguna Anda, dan bagaimana mereka akan menggunakan API Anda, maka itu akan membantu Anda memutuskan. Jika Anda membuat unggahan file menjadi sulit bagi pengguna API Anda maka mereka akan pindah, karena Anda akan menghabiskan banyak waktu untuk mendukungnya.

Yang kedua dari hal ini adalah dukungan alat yang Anda miliki untuk menulis API Anda dan betapa mudahnya bagi Anda untuk mengakomodasi satu mekanisme unggahan di atas yang lain.

Martin Peck
sumber
1
Hai, apakah ini berarti bahwa setiap kali kita memposting sesuatu ke server web, kita harus menyebutkan apa tipe-Konten untuk memberi tahu server web haruskah itu memecahkan kode data? Bahkan kami sendiri yang membuat permintaan http, kami HARUS menyebutkan jenis Konten, kan?
GMsoF
2
@ GMsoF, Ini opsional. Lihat stackoverflow.com/a/16693884/632951 . Anda mungkin ingin menghindari penggunaan tipe konten saat menyusun permintaan khusus untuk server tertentu untuk menghindari overhead umum.
Pacerier
2

Hanya sedikit petunjuk dari pihak saya untuk mengunggah data gambar kanvas HTML5:

Saya sedang mengerjakan proyek untuk toko cetak dan memiliki beberapa masalah karena mengunggah gambar ke server yang berasal dari canvaselemen HTML5 . Saya berjuang setidaknya selama satu jam dan saya tidak mendapatkannya untuk menyimpan gambar dengan benar di server saya.

Setelah saya mengatur contentTypeopsi panggilan ajax jQuery saya untuk application/x-www-form-urlencodedsemuanya berjalan dengan benar dan data base64-encoded ditafsirkan dengan benar dan berhasil disimpan sebagai gambar.


Mungkin itu membantu seseorang!

Torsten Barthel
sumber
4
Jenis konten apa yang dikirim sebelum Anda mengubahnya? Masalah ini bisa jadi karena server tidak mendukung jenis konten yang Anda kirim sebagai.
catorda
1

Jika Anda perlu menggunakan Content-Type = x-www-urlencoded-form maka JANGAN gunakan FormDataCollection sebagai parameter: Di asp.net Core 2+ FormDataCollection tidak memiliki konstruktor default yang diperlukan oleh Formatters. Gunakan IFormCollection sebagai gantinya:

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