Перейти к содержимому

Multipart form data что это

  • автор:

Form Data

Асинхронно отправлять JSON на сервер может каждый, а файлы — только Form Data .

Время чтения: 8 мин

Открыть/закрыть навигацию по статье

Обновлено 5 августа 2022

Кратко

Скопировать ссылку «Кратко» Скопировано

Form Data — это специальная коллекция данных, которая позволяет передавать данные в виде пар [ключ , значение ] на сервер при помощи fetch ( ) или XML Http Request . При этом используется точно такой же формат данных, какой использует тег с типом кодирования ‘multipart / form — data’ . Поэтому, значения в Form Data , как и у обычной HTML формы, могут быть только строками или файлами.

Пример

Скопировать ссылку «Пример» Скопировано

Предположим, что мы пишем функцию, которая отправляет на сервер два поля name и email . Значения полей она получает через аргументы:

 async function sendData(name, email)  const data = new FormData() data.append('name', name) data.append('email', email) return await fetch('/api/subscribe/',  method: 'POST', body: data, >)> async function sendData(name, email)  const data = new FormData() data.append('name', name) data.append('email', email) return await fetch('/api/subscribe/',  method: 'POST', body: data, >) >      

Данные отправляются на сервер с помощью объекта Form Data . Мы используем метод append ( ) чтобы добавить значения, а затем передаём полученный объект функции fetch ( ) .

Как пишется

Скопировать ссылку «Как пишется» Скопировано

Для работы с Form Data сначала с помощью конструктора new создаётся объект этого типа: const form = new Form Data ( ) . Затем у полученного объекта можно вызывать методы.

Основные методы для работы с Form Data :

  • append ( ключ , значение ) — добавляет значение для ключа с сохранением предыдущих значений;
  • set ( ключ , значение ) — устанавливает значение для ключа, перезаписывая предыдущие значения;
  • get ( ключ ) — возвращает первое значение ключа;
  • get All ( ключ ) — возвращает все значения ключа;
  • has ( ключ ) — проверяет наличие переданного ключа;
  • entries ( ) — возвращает итератор пар [ключ , значение ] ;
  • values ( ) — возвращает итератор всех значений коллекции;
  • keys ( ) — возвращает итератор всех ключей коллекции;
  • delete ( ключ ) — удаляет конкретное значение;

Как понять

Скопировать ссылку «Как понять» Скопировано

При отправке данных на сервер и сервер и клиент должны понимать друг друга, то есть использовали понятные обоим способы кодирования и декодирования данных. Таких способов существует большое количество и Form Data позволяет работать с одним из них — ‘multipart / form — data’ .

Form Data похожа на коллекцию Map — предоставляет удобные методы для добавления и удаления данных. Но если передать её в качестве тела запроса при вызове fetch ( ) (как в примере выше), данные «под капотом» будут преобразованы в нужный формат, а HTTP-заголовку Content — Type будет присвоено значение ‘multipart / form — data’ , чтобы сервер знал, что именно с этим форматом ему предстоит работать.

Form Data является отражением данных обычной HTML-формы с атрибутом enctype = «multipart / form — data» , поэтому пример выше можно представить следующим образом без JavaScript:

      form method="post" action="/api/subscribe/" enctype="multipart/form-data"> input type="text" name="name" value="" /> input type="email" name="email" value="" /> button type="submit">Отправитьbutton> form>      

Когда выбирать

Скопировать ссылку «Когда выбирать» Скопировано

Существует несколько самых популярных способов кодирования данных для отправки на сервер: ‘application / x — www — form — urlencoded’ , ‘multipart / form — data’ и ‘application / json’ . Иногда бывает так, что сервер поддерживает только какой-то определённый способ. Тогда выбирать не приходится. Но чаще всего современные решения на бэкенде поддерживают несколько способов, поэтому выбирать нужно в зависимости от задачи.

  • ‘application / x — www — form — urlencoded’ — способ, который используют HTML-формы по умолчанию. Из-за особенностей преобразования, этот способ плохо подходит для больших объёмов данных, в особенности, файлов или строк с большим количеством символов не из ASCII таблицы (например, символы русского алфавита).
  • ‘application / json’ — достаточно популярный формат из-за широкого распространения JSON как формата обмена данными. Из плюсов — поддерживает вложенные структуры, поэтому можно в одном запросе отправить, например, целый объект с данными. Однако, чтобы отправить файл при помощи этого формата, необходимо файл дополнительно закодировать в строку каким-нибудь алгоритмом, например Base64. Причём на сервере нужно декодировать эти данные обратно.
  • ‘multipart / form — data’ — удобный способ для загрузки файлов, оптимален с точки зрения размера закодированных данных, но в качестве значений может хранить только строки или файлы.

Поэтому, лучше всего использовать Form Data для отправки файлов на сервер или когда поддержка только строковых данных не является проблемой. Дополнительно, при создании Form Data можно передать DOM-элемент формы (будет рассмотрено ниже), и коллекция вытащит из этой формы все данные. Поэтому, если стоит задача отравить данные какой-либо формы, Form Data позволит сделать это с минимумом кода.

Создание Form Data

Скопировать ссылку «Создание FormData» Скопировано

Создать новый пустой объект Form Data можно с помощью конструктора:

 const data = new FormData() const data = new FormData()      

Также, конструктор может принимать в качестве аргумента DOM-элемент формы, в этом случае Form Data запишет текущие значения полей формы:

      form id="user-form"> input type="text" name="name" value="Аня" /> input type="text" name="language" value="JavaScript" /> form>      
 const form = document.querySelector('#user-form')const data = new FormData(form) for (let [key, value] of data)  console.log(`$ - $`)>// 'name - Аня'// 'language - JavaScript' const form = document.querySelector('#user-form') const data = new FormData(form) for (let [key, value] of data)  console.log(`$key> - $value>`) > // 'name - Аня' // 'language - JavaScript'      

Работа с коллекцией

Скопировать ссылку «Работа с коллекцией» Скопировано

Для добавления данных в коллекцию есть метод append ( ) :

 const data = new FormData()data.append('name', 'Вася') const data = new FormData() data.append('name', 'Вася')      

Теперь в коллекции появилось одно значение с ключом name и значением ‘Вася’.

Form Data может хранить несколько значений для одного ключа! Метод append ( ) , вызванный для ключа, по которому уже есть значение, добавит для этого ключа новые данные, не удаляя старые.

После выполнения этого кода, в коллекции будет два значения (‘Вася’ и ‘Лена’) для одного ключа name :

 const data = new FormData() data.append('name', 'Вася')data.append('name', 'Лена') const data = new FormData() data.append('name', 'Вася') data.append('name', 'Лена')      

Form Data поддерживает ещё один метод для записи данных: set ( ) . В отличие от append ( ) , он перезапишет старые данные для переданного ключа, если они были:

 const data = new FormData() data.set('name', 'Вася')data.set('name', 'Лена') const data = new FormData() data.set('name', 'Вася') data.set('name', 'Лена')      

В коллекции у ключа name будет одно значение ‘Лена’, потому что прошлое значение было перезаписано.

Form Data может хранить только строки или файлы в качестве значений. Все данные других типов будут преобразованы в строку.

Вот пример такого поведения. Записываем число 30 , но фактически записывается строка ’30’ :

 const data = new FormData() data.append('age', 30) console.log(data.get('age') === 30)// false console.log(data.get('age') === '30')// true console.log(typeof data.get('age'))// 'string' const data = new FormData() data.append('age', 30) console.log(data.get('age') === 30) // false console.log(data.get('age') === '30') // true console.log(typeof data.get('age')) // 'string'      

Для получения записанных значений есть два метода: get ( ) и get All ( ) . get ( ) вернёт первое значение для ключа или null , если для указанного ключа значений не было:

 const data = new FormData() console.log(data.get('name'))// null data.append('name', 'Вася')data.append('name', 'Лена')console.log(data.get('name'))// 'Вася' const data = new FormData() console.log(data.get('name')) // null data.append('name', 'Вася') data.append('name', 'Лена') console.log(data.get('name')) // 'Вася'      

В примере выше второе значение ‘Лена’ при помощи метода get ( ) недоступно, потому что он всегда возвращает только первое значение. Поэтому, чтобы получить все значения, на помощь приходит get All ( ) . Он всегда возвращает массив значений для указанного ключа. Если значений не было, возвращаемый массив будет пустой:

 const data = new FormData() console.log(data.getAll('name'))// [] data.append('name', 'Вася')data.append('name', 'Лена')console.log(data.getAll('name'))// ['Вася', 'Лена'] const data = new FormData() console.log(data.getAll('name')) // [] data.append('name', 'Вася') data.append('name', 'Лена') console.log(data.getAll('name')) // ['Вася', 'Лена']      

Чтобы проверить, есть ли в коллекции данные для определённого ключа, есть метод has ( ) , он вернёт true или false:

 const data = new FormData() console.log(data.has('name'))// false data.append('name', 'Вася') console.log(data.has('name'))// true const data = new FormData() console.log(data.has('name')) // false data.append('name', 'Вася') console.log(data.has('name')) // true      

Для удаления значений для определённого ключа можно воспользоваться методом delete ( ) . Важно помнить, что если у указанного ключа несколько значений, то удалятся все значения:

 const data = new FormData() data.append('name', 'Вася')data.append('name', 'Лена') data.delete('name') const data = new FormData() data.append('name', 'Вася') data.append('name', 'Лена') data.delete('name')      

После выполнения кода, коллекция снова будет пустая. Мы удалили ключ целиком, поэтому оба значения по этому ключу исчезли.

Обход значений

Скопировать ссылку «Обход значений» Скопировано

Form Data предоставляет встроенный итератор для обхода значений:

 const data = new FormData() data.append('name', 'Вася')data.append('name', 'Лена')data.append('language', 'JavaScript') for (let [key, value] of data)  console.log(`$ - $`)>// name - Вася// name - Лена// language - JavaScript const data = new FormData() data.append('name', 'Вася') data.append('name', 'Лена') data.append('language', 'JavaScript') for (let [key, value] of data)  console.log(`$key> - $value>`) > // name - Вася // name - Лена // language - JavaScript      

Тот же итератор доступен при помощи метода entries ( ) . Обратите внимание, что каждый элемент итератора — массив из двух элементов. Первый элемент — ключ, а второй — значение.

В дополнение к этому, Form Data предоставляет два других итератора: только ключей при помощи метода keys ( ) и только значений при помощи values ( ) . Каждый ключ при перечислении ключей будет появляться ровно столько раз, сколько значений он содержит:

 const data = new FormData() data.append('name', 'Вася')data.append('name', 'Лена')data.append('language', 'JavaScript') console.log('Проходимся по значениям:')for (let value of data.values())  console.log(value)>// 'Вася'// 'Лена'// 'JavaScript' console.log('Проходимся по ключам:')for (let key of data.keys())  console.log(key)>// 'name'// 'name' // 'language' const data = new FormData() data.append('name', 'Вася') data.append('name', 'Лена') data.append('language', 'JavaScript') console.log('Проходимся по значениям:') for (let value of data.values())  console.log(value) > // 'Вася' // 'Лена' // 'JavaScript' console.log('Проходимся по ключам:') for (let key of data.keys())  console.log(key) > // 'name' // 'name' // 'language'      

На практике

Скопировать ссылку «На практике» Скопировано

Виталий Баев советует

Скопировать ссылку «Виталий Баев советует» Скопировано

Сильной стороной Form Data является загрузка файлов на сервер. Если при использовании ‘application / json’ файлы необходимо дополнительно кодировать каким-то способом, чтобы привести к строке (и точно так-же декодировать на сервере), то Form Data умеет это делать «из коробки».

Например, если мы хотим после выбора файла сразу же загрузить его на сервер, то нам понадобится следующий HTML:

  input id="file-input" type="file" />      

А для отправки на сервер просто добавим файл в объект Form Data и отправим его на сервер:

 // Объявляем функцию загрузки файлаfunction sendFile(file)  const data = new FormData() // Добавляем файл data.append('document', file) return fetch('/api/upload/',  method: 'POST', body: data, >)> const fileInput = document.querySelector('#file-input')fileInput.addEventListener('change', (event) =>  // Получаем файл. Обратите внимание, что файлов может быть несколько если у инпута стоит атрибут `multiple` const file = event.target.files[0] // Отправляем файл на сервер при помощи созданной функции sendFile(file) // Очищаем текущее значение инпута. Если этого не делать, то при ошибке загрузки, повторный выбор того же файла не вызовет событие _change_ event.target.value = null>) // Объявляем функцию загрузки файла function sendFile(file)  const data = new FormData() // Добавляем файл data.append('document', file) return fetch('/api/upload/',  method: 'POST', body: data, >) > const fileInput = document.querySelector('#file-input') fileInput.addEventListener('change', (event) =>  // Получаем файл. Обратите внимание, что файлов может быть несколько если у инпута стоит атрибут `multiple` const file = event.target.files[0] // Отправляем файл на сервер при помощи созданной функции sendFile(file) // Очищаем текущее значение инпута. Если этого не делать, то при ошибке загрузки, повторный выбор того же файла не вызовет событие _change_ event.target.value = null >)      

В Доке есть отличная статья «Загрузка файла с прогресс-баром», где можно почитать подробнее про загрузку файлов с использованием Form Data .

FormData

В этой главе речь пойдёт об отправке HTML-форм: с файлами и без, с дополнительными полями и так далее. Объекты FormData помогут нам с этим. Как вы, наверняка, догадались по его названию, это объект, представляющий данные HTML формы.

let formData = new FormData([form]);

Если передать в конструктор элемент HTML-формы form , то создаваемый объект автоматически прочитает из неё поля.

Его особенность заключается в том, что методы для работы с сетью, например fetch , позволяют указать объект FormData в свойстве тела запроса body .

Он будет соответствующим образом закодирован и отправлен с заголовком Content-Type: multipart/form-data .

То есть, для сервера это выглядит как обычная отправка формы.

Отправка простой формы

Давайте сначала отправим простую форму.

Как вы видите, код очень компактный:

    

В этом примере серверный код не представлен, он за рамками этой статьи, он принимает POST-запрос с данными формы и отвечает сообщением «Пользователь сохранён».

Методы объекта FormData

С помощью указанных ниже методов мы можем изменять поля в объекте FormData :

  • formData.append(name, value) – добавляет к объекту поле с именем name и значением value ,
  • formData.append(name, blob, fileName) – добавляет поле, как будто в форме имеется элемент , третий аргумент fileName устанавливает имя файла (не имя поля формы), как будто это имя из файловой системы пользователя,
  • formData.delete(name) – удаляет поле с заданным именем name ,
  • formData.get(name) – получает значение поля с именем name ,
  • formData.has(name) – если существует поле с именем name , то возвращает true , иначе false

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

Ещё существует метод set , его синтаксис такой же, как у append . Разница в том, что .set удаляет все уже имеющиеся поля с именем name и только затем добавляет новое. То есть этот метод гарантирует, что будет существовать только одно поле с именем name , в остальном он аналогичен .append :

  • formData.set(name, value) ,
  • formData.set(name, blob, fileName) .

Поля объекта formData можно перебирать, используя цикл for..of :

let formData = new FormData(); formData.append('key1', 'value1'); formData.append('key2', 'value2'); // Список пар ключ/значение for(let [name, value] of formData) < alert(`$= $`); // key1=value1, потом key2=value2 >

Отправка формы с файлом

Объекты FormData всегда отсылаются с заголовком Content-Type: multipart/form-data , этот способ кодировки позволяет отсылать файлы. Таким образом, поля тоже отправляются, как это и происходит в случае обычной формы.

Пример такой формы:

 Картинка:   

Отправка формы с Blob-данными

Ранее в главе Fetch мы видели, что очень легко отправить динамически сгенерированные бинарные данные в формате Blob . Мы можем явно передать её в параметр body запроса fetch .

Но на практике бывает удобнее отправлять изображение не отдельно, а в составе формы, добавив дополнительные поля для имени и другие метаданные.

Кроме того, серверы часто настроены на приём именно форм, а не просто бинарных данных.

В примере ниже посылается изображение из и ещё несколько полей, как форма, используя FormData :

    

Пожалуйста, обратите внимание на то, как добавляется изображение Blob :

formData.append("image", imageBlob, "image.png");

Это как если бы в форме был элемент и пользователь прикрепил бы файл с именем «image.png» (3-й аргумент) и данными imageBlob (2-й аргумент) из своей файловой системы.

Сервер прочитает и данные и файл, точно так же, как если бы это была обычная отправка формы.

Итого

Объекты FormData используются, чтобы взять данные из HTML-формы и отправить их с помощью fetch или другого метода для работы с сетью.

Мы можем создать такой объект уже с данными, передав в конструктор HTML-форму – new FormData(form) , или же можно создать объект вообще без формы и затем добавить к нему поля с помощью методов:

  • formData.append(name, value)
  • formData.append(name, blob, fileName)
  • formData.set(name, value)
  • formData.set(name, blob, fileName)

Отметим две особенности:

  1. Метод set удаляет предыдущие поля с таким же именем, а append – нет. В этом их единственное отличие.
  2. Чтобы послать файл, нужно использовать синтаксис с тремя аргументами, в качестве третьего как раз указывается имя файла, которое обычно, при , берётся из файловой системы.
  • formData.delete(name)
  • formData.get(name)
  • formData.has(name)

Отправка multipart/form-data запроса в Qt

Иногда при разработке сетевого приложения возникает задача загрузки на сервер файла, и не просто так, а как части заполненной http формы. Это пример так называемого multipart/form-data запроса. Стандартные методы библиотеки Qt этого сделать не позволяют, поэтому приходится выкручиваться своими силами.

Общая информация

Итак, прежде всего необходимо понять, что же такого интересного содержит наш multipart/form-data запрос?
Если посмотреть на пример отсюда, типичный запрос представляет собой следующее:

POST http://www.site.ru/news.html HTTP/1.0\r\n Host: www.site.ru\r\n Referer: http://www.site.ru/index.html\r\n Cookie: income=1\r\n Content-Type: multipart/form-data; boundary=1BEF0A57BE110FD467A\r\n Content-Length: 209\r\n \r\n --1BEF0A57BE110FD467A\r\n Content-Disposition: form-data; name="login"\r\n \r\n Petya Vasechkin\r\n --1BEF0A57BE110FD467A\r\n Content-Disposition: form-data; name="password"\r\n \r\n qq\r\n --1BEF0A57BE110FD467A--\r\n


То, что нас особо интересует в заголовках — это boundary=1BEF0A57BE110FD467A и Content-Length: 209, после чего начинается тело запроса. Запрос состоит из нескольких частей, при этом разделителем будет считаться то, что написано как boundary, так же обязательно должна быть указана длина тела запроса — это поле Content-Length. Тело запроса — все начиная с первой строки —1BEF0A57BE110FD467A. В каждом разделе name — имя соответствующего поля формы, после двух переводов строк \r\n\r\n идет значение поля

Для отправки файла необходимо создать раздел следующего формата:

--1BEF0A57BE110FD467A\r\n Content-Disposition: form-data; name="news_file"; filename="news.txt"\r\n Content-Type: application/octet-stream\r\n Content-Transfer-Encoding: binary\r\n \r\n А вот такая новость, которая лежит в файле news.txt\r\n


Здесь дополнительно задается имя файла — news.txt, а так же кодировка данных в поле Content-Transfer-Encoding. Есть несколько разных кодировок, в том числе представленная binary — незакодированные данные. С учетом возможностей Qt, очень удобно использовать кодировку base64. Если файл не просто какой-то там (application/octet-stream), а известного типа, то можно в поле Content-Type этот тип указать, например Content-Type: image/png.

Простой пример

Перейдем к практическому примеру формирования запроса. У нас есть:

//язык С++ и библиотека Qt QNetworkAccessManager *manager; //параметр 1 - какое-то поле, параметр 2 - файл QByteArray param1Name="param1" ,param1Value="value1"; QByteArray param2Name="param2", param2FileName="news.txt", param2ContentType="text/plain",param2Data="А вот такая новость, которая лежит в файле news.txt"; 

Сформируем для начала тело запроса:

//задаем разделитель QByteArray postData,boundary="1BEF0A57BE110FD467A"; //первый параметр postData.append("--"+boundary+"\r\n");//разделитель //имя параметра postData.append("Content-Disposition: form-data; name=\""); postData.append(param1Name); postData.append("\"\r\n\r\n"); //значение параметра postData.append(param1Value); postData.append("\r\n"); //параметр 2 - файл postData.append("--"+boundary+"\r\n");//разделитель //имя параметра postData.append("Content-Disposition: form-data; name=\""); postData.append(param2Name); //имя файла postData.append("\"; filename=\""); postData.append(param2FileName); postData.append("\"\r\n"); //тип содержимого файла postData.append("Content-Type: "+param2ContentType+"\r\n"); //передаем в base64 postData.append("Content-Transfer-Encoding: base64\r\n\r\n"); //данные postData.append(param2Data.toBase64()); postData.append("\r\n"); //"хвост" запроса postData.append("--"+boundary+"--\r\n"); 

В переменной postData получаем готовое тело запроса — осталось только отослать и не забыть установить дополнительные заголовки запроса:

Атрибут enctype

Определяет способ кодирования данных формы при их отправке на сервер. Обычно устанавливать значение атрибута enctype не требуется, данные вполне правильно понимаются на стороне сервера. Однако если используется поле для отправки файла ( input type=»file» ), следует определить атрибут enctype как multipart/form-data .

Синтаксис

Значения

application/x-www-form-urlencoded Вместо пробелов ставится +, символы вроде русских букв кодируются их шестнадцатеричными значениями (например, %D0%90%D0%BD%D1%8F вместо Аня). multipart/form-data Данные не кодируются. Это значение применяется при отправке файлов. text/plain Пробелы заменяются знаком +, буквы и другие символы не кодируются.

Значение по умолчанию

Пример

Результат данного примера показан на рис. 1.

Поле для отправки файла

Рис. 1. Поле для отправки файла

Браузеры

4 12 1 4 3 1

В таблице браузеров применяются следующие обозначения.

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

Число указывает версию браузреа, начиная с которой элемент поддерживается.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *