application / x-www-form-urlencoded или multipart / form-data?

1337

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

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

В основном я не нашел никаких официальных указаний относительно использования различных типов контента.

Максимум
источник
75
Следует отметить, что эти два типа MIME используются в HTML-формах. Сам по себе HTTP не имеет таких ограничений ... можно использовать любой тип MIME, какой он пожелает, через HTTP.
tybro0103

Ответы:

2015

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 (как описано в других ответах). Части разделены определенной границей строки (выбранной специально, чтобы эта строка границы не встречалась ни в одной из полезных нагрузок "value"). Каждая часть имеет свой собственный набор заголовков MIME Content-Type, в частности Content-Disposition, который может дать каждой части свое «имя». Часть значения каждой пары имя / значение является полезной нагрузкой каждой части сообщения MIME. Спецификация MIME дает нам больше возможностей при представлении значения полезной нагрузки - мы можем выбрать более эффективное кодирование двоичных данных для экономии полосы пропускания (например, base 64 или даже необработанный двоичный файл).

Почему бы не использовать multipart/form-dataвсе время? Для коротких буквенно-цифровых значений (как и для большинства веб-форм) затраты на добавление всех заголовков MIME значительно перевесят любую экономию от более эффективного двоичного кодирования.

Мэтт Бриджес
источник
84
Имеет ли x-www-form-urlencoded ограничение по длине или он неограничен?
Pacerier
35
@Pacerier Ограничение применяется сервером, получающим запрос POST. См. Эту тему для дальнейшего обсуждения: stackoverflow.com/questions/2364840/…
Мэтт Бриджес
5
@ZiggyTheHamster JSON и BSON более эффективны для разных типов данных. Base64 уступает gzip, для обоих методов сериализации. Base64 не дает никаких преимуществ, HTTP поддерживает бинарные загрузки.
Tiberiu-Ionuț Stan
16
Также обратите внимание, что если форма содержит загрузку именованного файла, ваш единственный выбор - данные формы, потому что urlencoded не может разместить имя файла (в данных формы это параметр name для content-disposition).
Гвидо ван Россум
4
@EML смотрите в скобках "(выбрано специально, чтобы эта граничная строка не встречалась ни в одной из полезных нагрузок" value ")"
Мэтт Бриджес
152

ЧИТАЙТЕ МЕНЬШЕ ПЕРВЫЙ ПАРА ЗДЕСЬ!

Я знаю, что это на 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выводе.

EML
источник
52
Хотя рассмотрение multipart / form-data является гарантией того, что граница не появляется в данных, это довольно просто сделать, выбрав достаточно длинную границу. Пожалуйста, не используйте base64-кодировку для этого. Граница, которая генерируется случайным образом и имеет ту же длину, что и UUID, должна быть достаточной: stackoverflow.com/questions/1705008/… .
Джошкодес
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
Этот ответ не только ничего не добавляет к обсуждению, но и дает неправильные советы. Во-первых, всякий раз, когда передаются случайные данные в отдельных частях, всегда возможно, что выбранная граница будет присутствовать в полезной нагрузке. Единственный способ убедиться, что этого не произойдет, - изучить всю полезную нагрузку для каждой границы, с которой мы столкнулись. Совершенно непрактично. Мы просто принимаем бесконечно малую вероятность столкновения и получаем разумную границу, например "--- border- <UUID here> -boundary ---". Во-вторых, всегда использование Base64 приведет к потере пропускной способности и заполнению буферов без всякой причины.
vagelis
92

Я не думаю, что HTTP ограничен POST в multipart или 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, по-прежнему текст, но вы можете сжать
  • не ascii chars: JSON является UTF-8
  • двоичные данные: base64 (также см. json-binary-question )
  • данные имени файла: инкапсулировать как собственный раздел поля внутри 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 - это колокольчик в мыслях о вложениях (внутри тела ответа несколько разделов данных объединяются с определенной граничной строкой).
Мануэль Алдана
4
Я думаю, что OP, вероятно, просто спрашивал о двух типах, которые используют формы HTML, но я рад, что на это было указано.
tybro0103
30

Я согласен со многим, что сказал Мануэль. На самом деле, его комментарии относятся к этому URL ...

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

... в котором говорится:

Тип содержимого «application / x-www-form-urlencoded» неэффективен для отправки больших объемов двоичных данных или текста, содержащего символы не ASCII. Тип контента "multipart / form-data" должен использоваться для отправки форм, которые содержат файлы, данные не ASCII и двоичные данные.

Тем не менее, для меня все сводится к поддержке инструмента / фреймворка.

  • С какими инструментами и средами вы ожидаете, что ваши пользователи API будут создавать свои приложения?
  • Есть ли у них фреймворки или компоненты, которые они могут использовать в пользу одного метода перед другим?

Если вы получите четкое представление о ваших пользователях и о том, как они будут использовать ваш API, это поможет вам принять решение. Если вы сделаете загрузку файлов трудной для ваших пользователей API, они уйдут, и вы потратите много времени на их поддержку.

Вторичным по отношению к этому будет инструмент поддержки, который вы имеете для написания своего API, и насколько легко для вас приспособить один механизм загрузки к другому.

Мартин Пек
источник
1
Привет, значит ли это, что каждый раз, когда мы публикуем что-то на веб-сервере, мы должны упоминать, что такое Content-type, чтобы веб-сервер знал, должен ли он декодировать данные? Даже если мы создадим запрос http самостоятельно, мы ДОЛЖНЫ упомянуть право Content-type?
GMsoF
2
@GMsoF, это необязательно. См. Stackoverflow.com/a/16693884/632951 . Возможно, вы захотите избежать использования типа контента при создании конкретного запроса для конкретного сервера, чтобы избежать общих накладных расходов.
Pacerier
2

Небольшая подсказка с моей стороны для загрузки данных изображения холста HTML5:

Я работаю над проектом для типографии, и у меня возникли некоторые проблемы из-за загрузки изображений на сервер из canvasэлемента HTML5 . Я боролся, по крайней мере, час, и я не получил его, чтобы правильно сохранить изображение на моем сервере.

Как только я установил contentTypeопцию моего вызова jQuery ajax, application/x-www-form-urlencodedвсе прошло правильно, и данные, закодированные в base64, были правильно интерпретированы и успешно сохранены в виде изображения.


Может быть, это кому-то помогает!

Торстен Бартел
источник
4
Какой тип контента он отправлял до того, как вы его изменили? Эта проблема могла быть вызвана тем, что сервер не поддерживает тип контента, который вы отправляли.
catorda
1

Если вам нужно использовать Content-Type = x-www-urlencoded-form, тогда НЕ используйте FormDataCollection в качестве параметра: в asp.net Core 2+ FormDataCollection не имеет конструкторов по умолчанию, которые требуются для Formatters. Вместо этого используйте IFormCollection:

 public IActionResult Search([FromForm]IFormCollection type)
    {
        return Ok();
    }
jahansha
источник