application / x-www-form-urlencoded або multipart / form-data?


1335

У HTTP є два способи розміщення даних POST: application/x-www-form-urlencodedі multipart/form-data. Я розумію, що більшість браузерів можуть завантажувати файли, лише якщо multipart/form-dataвони використовуються. Чи є якісь додаткові вказівки щодо використання одного з типів кодування в контексті API (браузер не бере участь)? Це може, наприклад, базуватися на:

  • розмір даних
  • існування символів, що не належать до ASCII
  • існування на (некодованих) двійкових даних
  • необхідність передачі додаткових даних (наприклад, ім'я файлу)

Я в основному не знайшов офіційних вказівок в Інтернеті щодо використання різних типів вмісту до цих пір.


74
Слід зазначити, що це два типи MIME, які використовують форми HTML. HTTP сам по собі не має таких обмежень ... можна використовувати будь-який тип MIME, який він хоче через HTTP.
tybro0103

Відповіді:


2013

TL; DR

Підсумок; якщо у вас є бінарні (не алфавітно-цифрові) дані (або значне корисне навантаження) для передачі, використовуйте multipart/form-data. В іншому випадку використовуйте application/x-www-form-urlencoded.


Згадані вами типи MIME - це два Content-Typeзаголовки для HTTP POST-запитів, які повинні підтримувати користувальницькі агенти (браузери). Мета обох цих типів запитів - надіслати на сервер список пар імен / значень. Залежно від типу та кількості даних, що передаються, один із методів буде більш ефективним, ніж інший. Щоб зрозуміти чому, ви повинні подивитися, що кожен робить під прикриттями.

Тому application/x-www-form-urlencoded, що тіло повідомлення HTTP, надіслане серверу, по суті є однією гігантською рядком запиту - пари імен / значень розділені амперсандом ( &), а імена відокремлені від значень символом рівним ( =). Прикладом цього може бути: 

MyVariableOne=ValueOne&MyVariableTwo=ValueTwo

Відповідно до специфікації :

[Зарезервовані та] не алфавітно-цифрові символи замінюються на "% HH", знак відсотка та дві шістнадцяткові цифри, що представляють ASCII код символу

Це означає, що для кожного не алфавітно-цифрового байта, який існує в одному з наших значень, для його представлення потрібно три байти. У великих бінарних файлах утроєне корисне навантаження буде дуже неефективним.

Ось де це multipart/form-dataприходить. При такому способі передачі пар імен / значень кожна пара представлена ​​як "частина" у повідомленні MIME (як описано в інших відповідях). Частини розділені певною межею рядка (вибрано спеціально таким чином, щоб ця гранична рядок не виникала в жодному з корисних навантажень "значення"). Кожна частина має власний набір MIME заголовків, як Content-Type, зокрема Content-Disposition, які можуть дати кожній частині її "ім'я". Елемент значення кожної пари імен / значень є корисним завантаженням кожної частини повідомлення MIME. Специфікація MIME надає нам більше варіантів при поданні значення корисного навантаження - ми можемо вибрати більш ефективне кодування бінарних даних для збереження пропускної здатності (наприклад, база 64 або навіть необроблена двійкова).

Чому б не використовувати multipart/form-dataвесь час? Що стосується коротких буквено-цифрових значень (як і більшість веб-форм), накладні витрати на додавання всіх заголовків MIME значно перевищують будь-яку економію від більш ефективного двійкового кодування.


84
Чи має код x-www-form-urlencoded обмеження довжини чи він необмежений?
Pacerier

34
@Pacerier Ліміт застосовується сервером, який отримує запит POST. Дивіться цю тему для більшого обговорення: stackoverflow.com/questions/2364840/…
Метт Бріджз

5
@ZiggyTheHamster JSON і BSON є більш ефективними для різних типів даних. Для обох методів серіалізації Base64 поступається gzip. Base64 взагалі не приносить переваг, HTTP підтримує бінарний pyloads.
Tiberiu-Ionuț Stan

16
Також зауважте, що якщо форма містить назву завантаження файлу, ваш єдиний вибір - це форма даних, оскільки urlencoded не має способу розміщення імені файлу (у data-data - це параметр імені до вмісту-диспозиції).
Гідо ван Россум

4
@EML див. Мої дужки "(вибрано спеціально для того, щоб ця межа не виникала в жодному з" значень "корисних навантажень)"
Метт Бріджз

151

ЧИТАТИ ОСТАННЯ ПЕРШОГО ПАРА ТУТ!

Я знаю, що це на 3 роки занадто пізно, але відповідь Метта (прийнята) не є повною, і врешті-решт у вас виникнуть проблеми. Ключовим тут є те, що якщо ви вирішили використовувати multipart/form-data, межа не повинна відображатися у файлових даних, які сервер врешті отримує.

Це не проблема application/x-www-form-urlencoded, тому що немає меж. x-www-form-urlencodedтакож завжди може обробляти двійкові дані, за допомогою простого доцільного перетворення одного довільного байта в три 7BITбайти. Неефективна, але вона працює (зауважте, що коментар про неможливість надсилання імен файлів, а також бінарних даних невірний; ви просто надсилаєте їх як іншу пару ключів / значень).

Проблема multipart/form-dataполягає в тому, що розділовий пристрій не повинен бути присутнім у файлових даних (див. RFC 2388 ; розділ 5.2, також містить досить кульгавий привід для відсутності належного сукупного типу MIME, що дозволяє уникнути цієї проблеми).

Отже, на перший погляд, не multipart/form-dataмає жодної цінності у будь-якому завантаженні файлів, бінарному чи іншому. Якщо ви не вибрали кордон правильно, то з часом у вас виникне проблема, чи ви надсилаєте звичайний текст чи необроблений бінарний файл - сервер знайде межу в неправильному місці, а ваш файл буде усічений або POST не вдасться.

Ключ полягає у виборі кодування та межі, щоб вибрані граничні символи не могли відображатися у кодованому виході. Одне просте рішення - це використання base64( не використовуйте сирі двійкові). У base64 3 довільні байти кодуються чотирма 7-бітовими символами, де набір вихідних символів [A-Za-z0-9+/=](тобто буквено-цифрові символи, '+', '/' або '='). =є окремим випадком і може з’являтися лише в кінці кодованого виводу, як одинарний, =або як подвійний ==. Тепер виберіть свою межу як 7-бітну рядок ASCII, яка не може відображатися у base64висновку. Багато варіантів, які ви бачите в мережі, не проходять цей тест - MDN формує документинаприклад, використовувати "blob" в якості межі при надсиланні двійкових даних - не добре. Однак щось на кшталт "! Blob!" ніколи не з’явиться у base64виході.


52
Хоча врахування багаточастинних / форм-даних є тим, що межа не відображається в даних, це зробити досить просто, вибравши достатньо довгий кордон. Будь ласка, не використовуйте кодування base64 для цього. Межа, яка генерується випадковим чином і такої ж довжини, що і UUID, повинна бути достатньою: stackoverflow.com/questions/1705008/… .
Joshcodes

20
@EML, це зовсім не має сенсу. Очевидно, що кордон http (браузер) обирається автоматично, і клієнт буде досить розумним, щоб не використовувати межу, що зіткнеться із вмістом завантажених файлів. Це так само просто збігається підрядка index === -1.
Pacerier

13
@Pacerier: (A) прочитайте питання: "Браузер не задіяний, контекст API". (B) браузери в жодному разі не створюють для вас запитів. Ви робите це самостійно, вручну. У браузерах немає ніякої магії.
EML

12
@BeniBela, він, мабуть, запропонує скористатися '()+-./:=тоді. Але випадкова генерація з підрядком перевірки ще шлях , і це можна зробити з допомогою одного рядка: while(true){r = rand(); if(data.indexOf(r) === -1){doStuff();break;}}. Пропозиція EML (перетворити на base64 просто для уникнення відповідності підрядків) є просто дивним, не кажучи вже про непотрібну деградацію продуктивності. І все без проблем, оскільки однорядковий алгоритм однаково простий і простий. Base64 не передбачається (ab), що використовується таким чином, оскільки тіло HTTP приймає всі 8-бітні октети.
Pacerier

31
Ця відповідь не тільки нічого не додає до дискусії, але й дає неправильну пораду. По-перше, щоразу, коли передаються випадкові дані в окремих частинах, завжди можливо, що обрана межа буде присутня у корисному навантаженні. ТІЛЬКИ, щоб переконатися, що цього не відбувається, - це вивчити весь корисний вантаж для кожної межі, яку ми придумали. Зовсім непрактично. Ми просто приймаємо нескінченно малу ймовірність зіткнення і придумуємо розумну межу, наприклад "--- межа- <UUID тут> -границя ---". По-друге, завжди використання Base64 марнуватиме пропускну здатність і заповнювати буфери без будь-якої причини.
vagelis

92

Я не думаю, що HTTP обмежений POST у багаточастинному або x-www-form-urlencoded. Content-Type заголовка ортогональна методу HTTP POST (ви можете заповнити тип MIME , який підходить вам). Це також стосується типових веб-представлень на базі HTML-репрезентації (наприклад, корисна навантаження json стала дуже популярною для передачі корисного навантаження для запитів ajax).

Щодо Restful API через HTTP, найпопулярніші типи контенту, з якими я зв’язався, це application / xml та application / json.

додаток / xml:

  • розмір даних: XML дуже багатослівний, але зазвичай це не проблема, коли використовується стиснення та думка, що випадок доступу до запису (наприклад, через POST або PUT) набагато рідше, ніж доступ для читання (у багатьох випадках це <3% усього трафіку) ). Рідко трапляються випадки, коли мені доводилося оптимізувати ефективність запису
  • існування символів не-ascii: ви можете використовувати utf-8 як кодування в XML
  • існування двійкових даних: потрібно було б використовувати кодування base64
  • дані про ім’я файлу: це внутрішнє поле можна інкапсулювати в XML

додаток / json

  • розмір даних: компактніший менше, ніж XML, нерухомий текст, але його можна стискати
  • non-ascii chars: json is utf-8
  • двійкові дані: base64 (також див. json-бінарне запитання )
  • дані імені файлу: інкапсулювати як власний розділ поля всередині json

бінарні дані як власний ресурс

Я б спробував представити двійкові дані як власний актив / ресурс. Це додає ще один дзвінок, але декупаж краще виконувати. Приклади зображень:

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

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

У наступних ресурсах ви можете просто вкласти бінарний ресурс як посилання:

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

Цікаво. Але коли використовувати application / x-www-form-urlencoded та коли multipart / form-data?
макс

3
application / x-www-form-urlencoded є типом mime для вашого запиту (див. також w3.org/TR/html401/interact/forms.html#h-17.13.4 ). Я використовую його для "звичайних" веб-форм. Для API я використовую application / xml | json. multipart / form-data - це дзвінок, що роздумує над вкладеннями (всередині органу відповіді кілька розділів даних конкретизовано певним граничним рядком).
manuel aldana

4
Я думаю, що ОП, ймовірно, просто запитував про два типи, якими користуються HTML-форми, але я радий, що це було зазначено.
tybro0103

30

Я дуже згоден з тим, що сказав Мануель. Насправді його коментарі стосуються цієї URL-адреси ...

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

... де зазначено:

Тип вмісту "application / x-www-form-urlencoded" неефективний для надсилання великої кількості бінарних даних або тексту, що містить символи, що не містять ASCII. Тип вмісту "багаточастинні / форми-дані" слід використовувати для подання форм, що містять файли, не-ASCII дані та двійкові дані.

Однак для мене це зводиться до підтримки інструментів / рамок.

  • З якими інструментами та рамками ви очікуєте, що ваші користувачі API будуватимуть свої додатки?
  • Чи є у них рамки чи компоненти, якими вони можуть скористатися одним методом над іншим?

Якщо ви отримаєте чітке уявлення про своїх користувачів та як вони використовуватимуть ваш API, то це допоможе вам вирішити. Якщо ви завантажите файли важкими для своїх користувачів API, вони відійдуть, ви витратите багато часу на їх підтримку.

Вторинним для цього є підтримка, яку ви маєте для написання вашого API, і як легко розмістити один механізм завантаження над іншим.


1
Привіт, чи означає це, що кожного разу, коли ми публікуємо щось на веб-сервері, ми мусимо згадати, що таке тип вмісту для того, щоб веб-сервер знав, чи повинен він декодувати дані? Навіть ми самі розробляємо http-запит, МОЖЕ ВЗАЄМО згадати тип вмісту?
GMsoF

2
@GMsoF, це необов’язково. Дивіться stackoverflow.com/a/16693884/632951 . Ви можете уникати використання типу вмісту під час створення конкретного запиту для певного сервера, щоб уникнути загальних накладних витрат.
Pacerier

2

Трохи підказки з мого боку для завантаження зображень полотна HTML5:

Я працюю над проектом для друкарні, і у мене виникли деякі проблеми через завантаження зображень на сервер, що надходили з canvasелемента HTML5 . Я боровся принаймні годину, і я не отримав цього, щоб правильно зберегти зображення на своєму сервері.

Одного разу я встановив contentTypeможливість мого виклику jQuery ajax, щоб application/x-www-form-urlencodedвсе пішло правильно, і кодовані базовими кодами дані були інтерпретовані правильно та успішно збережені як зображення.


Може, це комусь допомагає!


4
Який тип вмісту він надсилав, перш ніж змінити його? Ця проблема могла бути пов’язана з тим, що сервер не підтримує тип вмісту, яким ви його надсилали.
catorda

1

Якщо вам потрібно використовувати Content-Type = x-www-urlencoded-form, то НЕ використовуйте FormDataCollection як параметр: У asp.net Core 2+ FormDataCollection не має конструкторів за замовчуванням, які потрібні форматерам. Використовуйте замість IFormCollection:

 public IActionResult Search([FromForm]IFormCollection type)
    {
        return Ok();
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.