POST запрос, составное содержимое (multipart/form-data)
Передача составных данных методом POST
В жизни любого программиста попадаются задачки, которые человека цепляют. Вот не нравится стандартный метод решения и все! А порой бывает, что стандартные решения не подходят по какой-то причине. Некоторые люди обходят такие задачи стороной, другие же любят решать их. Можно даже сказать сами их находят. Одна из таких задач отсылка файла или несколько файлов методом POST.
Некоторые наверное скажут, эта задача совсем не задача. Ведь есть замечательная библиотека CURL, которая довольно простая и решает эту задачу легко! Но не спешите. Да, CURL мощная библиотека, да она загружает файлы, но… Как Вы знаете у нее есть маленькая особенность — файл должен быть размещен на жестком диске!
А теперь давайте представим себе такую ситуацию, Вы генерируете динамически файл или же он уже находится в памяти и нужно его отправить методом POST на удаленный Web сервер. Что же тогда получается? Перед его отправкой нужно его сохранить? Да именно так и поступило бы 90% программистов. Зачем искать лишние проблемы, если решение лежит на поверхности? Но мы же с Вами не из этих 90%! Мы же лучше, мы же можем решить любую задачку. Зачем нам лишнее действие? Во-первых, оно задействует не быструю файловую систему жесткого диска. Во-вторых, у нас может и не быть доступа к файловой системе или же там выделено слишком мало места.
Как же нам тогда решить эту задачку? Для этого надо взглянуть как собственно передаются данные методом POST. Единственный вариант решения — это передача файла составным запросом с помощью multipart/form-data. Этот метод хорошо описан в RFC7578. Давайте взглянем как будет выглядеть тело POST запроса multipart/form-data:
Наше тело состоит из двух частей, в первой части мы передаем значение поля формы name=«field» равное: text. Во второй части мы передаем поле name=«file» с содержимым файла filename=«sample.txt»: Content file. В заголовке мы указываем формат содержимого POST запроса — Content-Type: multipart/form-data, строку разделитель составных частей: boundary=————-573cf973d5228 и длину сообщения — Content-Length: 288.
Осталось, собственно, написать программу реализующий этот метод. Так как мы люди умные и не пишем по сто раз одно и тоже в разных проектах, то оформим все в виде класса реализующий этот метод. Плюс к этому, расширим его для разных вариантов отправки как файлов, так и простых элементов формы. А что бы отличить среди массива POST данных, наличие файла, создадим отдельный файл — контейнер с содержимым файла и его данных (имя и расширение). Таким образом он будет выглядеть следующим образом:
Теперь собственно сам класс по формированию тела multipart/form-data для POST запроса:
Данный класс состоит из нескольких методов. Метод — PartPost формирует отдельные части составного запроса, а метод — Get объединяет эти части и формирует тело POST запроса в формате — multipart/form-data.
Теперь у нас есть универсальный класс для отправки тела POST запроса. Осталось написать программу использующую данный класс для отправки файлов на удаленный Web сервер. Воспользуемся библиотекой CURL:
Если CURL не подходит, то данную библиотеку можно применить и для отправки через сокеты. Ну и собственно ссылки на источники:
Кроссбраузерная отправка формы с файлом или как переписать весь отправщик несколько раз после тестирования в IE
Задача: отправка и обработка файлов с помощью FormData и FileReader в форме со всеми возможными полями и пересылкой дополнительных параметров для каждого поля c объединением всех данных формы (кроме файлов и системных полей) в общий массив.
Поддержка: все современные браузеры, IE 10+.

Для начала разберемся, что же такое FormData
Formdata — тип данных в рамках технологии XHR2, данные в нем хранятся в виде пар ключ / значение.
new Formdata () — это конструктор для создания объекта FormData.
FormData имеет множество методов для полноценной работы с ней, таких как:
В начале работы с FormData появилась весьма сложная проблема из-за того что встал вопрос: как можно перебрать данные в этом объекте? На русскоязычных ресурсах данных найдено не было, зато при получении списка всех методов объекта был найден forEach(), который позволил очень легко перебирать данные. Но появилась проблема, связанная с поддержкой браузерами. Так что этот метод не годится — нужна полная поддержка.
Также FormData можно перебирать с помощью цикла for. of (доступно в ECMAScript 6, с нативной поддержкой которого также есть проблемы).
Главная проблема FormData заключается в Internet explorer (как всегда), а вернее, в его поддержке. Из всех методов, которые есть в FormData, Internet explorer поддерживает только append(), что уничтожает всю простоту использования. Следовательно, мы не можем собрать форму с помощью простого вызова конструктора и последующего изменения данных в ней, и придется это делать вручную:
Теперь познакомимся с FileReader
FileReader — это объект, который позволяет веб-приложениям асинхронно читать содержимое файлов (или буферы данных), хранящиеся на компьютере пользователя, используя объекты File или Blob, с помощью которых задается файл или данные для чтения.
С его помощью мы будем отслеживать загрузку файлов на клиенте, формировать список загруженных файлов и выводить для них прогресс бар.
Теперь к самой задаче
Форма, которую мы будет пересылать:
Для удобства пользователей предоставим им возможность добавления сразу большого количества файлов. С этой целью укажем в поле name значение file[] и атрибут multiple, с ограничением только картинки accept=«image».
Для пользователей также будем выводить список файлов, которые они загрузили с раздельным progress bar-ом для каждого файла и возможностью удаления перед отправкой. И тут мы столкнулись с проблемой. Дело в том, что fileList (массив загруженных файлов) у нашего input предназначен только для чтения, и удалить только выбранный пользователем файл мы не можем. Так что было решено перед отправкой на сервер сверять список, который уже сформировал пользователь, с тем что уже загружено. И при совпадении со списком файл будет добавляться в FormData.
1) Создаем саму функцию отправки через ajax:
2) Создаем функцию сборки формы:
3) Оборачиваем все это в функцию для удобного вызова по событию:
4) Вешаем функцию на событие клика на кнопку отправки формы:
Теперь у нас есть полноценный отправщик формы, осталось только написать обработчик для файлов.
1) Создадим функцию отслеживания состояния input type=file:
2) Напишем обработчик ошибок:
3) Напишем функцию для переборки файлов в fileList нашего input type=file:
4) Теперь непосредственно сам обработчик:
5) Добавим возможность удаления файлов из списка:
6) Можем использовать наш отправщик, не забыв поднять локальный сервер:
XMLHttpRequest POST, формы и кодировка
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/xmlhttprequest.
Во время обычной отправки формы
* ‘ ( ) заменяются на их цифровой код в UTF-8 со знаком %.
в JavaScript есть функция encodeURIComponent для получения такой кодировки «вручную»:
Эта кодировка используется в основном для метода GET, то есть для передачи параметра в строке запроса. По стандарту строка запроса не может содержать произвольные Unicode-символы, поэтому они кодируются как показано выше.
GET-запрос
Поэтому в некоторых фреймворках, чтобы сказать серверу, что это AJAX, добавляют специальный заголовок, например такой:
POST с urlencoded
В стандартных HTTP-формах для метода POST доступны три кодировки, задаваемые через атрибут enctype :
В зависимости от enctype браузер кодирует данные соответствующим способом перед отправкой на сервер.
В случае с XMLHttpRequest мы, вообще говоря, не обязаны использовать ни один из этих способов. Главное, чтобы сервер наш запрос понял. Но обычно проще всего выбрать какой-то из стандартных.
Для примера отправим запрос в кодировке application/x-www-form-urlencoded :
Всегда используется только кодировка UTF-8, независимо от языка и кодировки страницы.
Если сервер вдруг ожидает данные в другой кодировке, к примеру windows-1251, то их нужно будет перекодировать.
Кодировка multipart/form-data
Кодировка urlencoded за счёт замены символов на %код может сильно «раздуть» общий объём пересылаемых данных. Поэтому для пересылки файлов используется другая кодировка: multipart/form-data.
В этой кодировке поля пересылаются одно за другим, через строку-разделитель.
Чтобы использовать этот способ, нужно указать его в атрибуте enctype и метод должен быть POST:
Форма при такой кодировке будет выглядеть примерно так:
…То есть, поля передаются одно за другим, значения не кодируются, а чтобы было чётко понятно, какое значение где – поля разделены случайно сгенерированной строкой, которую называют «boundary» (англ. граница), в примере выше это RaNdOmDeLiMiTeR :
Такой способ используется в первую очередь при пересылке файлов, так перекодировка мегабайтов через urlencoded существенно загрузила бы браузер. Да и объём данных после неё сильно вырос бы.
Однако, никто не мешает использовать эту кодировку всегда для POST запросов. Для GET доступна только urlencoded.
POST с multipart/form-data
Сделать POST-запрос в кодировке multipart/form-data можно и через XMLHttpRequest.
Достаточно указать в заголовке Content-Type кодировку и границу, и далее сформировать тело запроса, удовлетворяющее требованиям кодировки.
Пример кода для того же запроса, что и раньше, теперь в кодировке multipart/form-data :
Тело запроса будет иметь вид, описанный выше, то есть поля через разделитель.
Можно создать запрос, который сервер воспримет как загрузку файла.
Для добавления файла нужно использовать тот же код, что выше, модифицировав заголовки перед полем, которое является файлом, так:
FormData
Современные браузеры, исключая IE9- (впрочем, есть полифил), поддерживают встроенный объект FormData, который кодирует формы для отправки на сервер.
Это очень удобно. Например:
Другие кодировки
XMLHttpRequest сам по себе не ограничивает кодировку и формат пересылаемых данных.
Поэтому для обмена данными часто используется формат JSON:
Итого
В XMLHttpRequest можно использовать и другие HTTP-методы, например PUT, DELETE, TRACE. К ним применимы все те же принципы, что описаны выше.
Загрузка файлов на сервер с использованием HTTP-сервиса 1С (multipart/form-data).
Предположим, что есть сайт (HTML-страница) с формой, в которой пользователь должен заполнить некие поля, прикрепить файл и отправить все эти данные на сервер, чтобы сохранить их в базе 1С, где-то на диске или просто обработать по какому-либо алгоритму. Например, прикрепить скан паспорта в личном кабинете или скриншот при описании своей проблемы с программой и т.п.
Для отправки HTML-форм с двоичными данными методом POST используется составной тип содержимого multipart/form-data.
Получение таких данных HTTP-сервисом 1С реализовано на примере демонстрационной базы. В базе присутствует справочник Пользователи, содержащий имя пользователя, адрес его электронной почты и фотографию. А так же HTTP-сервис для регистрации новых пользователей.
Реализована простейшая HTML-страница регистрации пользователя на сайте.
После нажатия на кнопку Выполнить регистрацию в демонстрационной базе создаётся новый элемент справочника Пользователи, содержащий указанные данные.
Тело передаваемого POST запроса будет выглядеть следующим образом.
Подробно этот метод описан в RFC7578.
Функции по обработке HTTP-запроса с составным содержимым реализованы в общем модуле ОбработкаЗапросовHTTPСервиса. Его можно использовать «как есть» в своих конфигурациях.
Процедура обработчика HTTP-сервиса по регистрации нового пользователя может выглядеть подобным образом.
Функция ПрочитатьСоставноеСодержимоеЗапроса разбирает полученный запрос и возвращает Соответствие, содержащее переданные поля с их содержимым.
Синтаксис:
Параметры:
(обязательный)
Тип: HTTPСервисЗапрос
Запрос, полученный HTTP-сервисом.
Возвращаемое значение:
Тип: Соответствие
Соответствие, содержащее описание полей составного содержимого. В качестве ключа используется имя поля. Значение содержит структуру с описанием поля:
Описание:
Читает тело запроса HTTP-сервиса, имеющее составное содержимое (multipart/form-data) и возвращает поля этого содержимого в виде соответствия. В качестве ключа используется имя поля.
В архиве к публикации содержится cf-файл демонстрационной конфигурации и html-страница регистрации пользователя. Также, ниже приведён полный текст общего модуля ОбработкаЗапросовHTTPСервиса, который можно вставить в свою конфигурацию.
FormData
В этой главе речь пойдёт об отправке HTML-форм: с файлами и без, с дополнительными полями и так далее. Объекты FormData помогут нам с этим. Как вы, наверняка, догадались по его названию, это объект, представляющий данные HTML формы.
То есть, для сервера это выглядит как обычная отправка формы.
Отправка простой формы
Давайте сначала отправим простую форму.
Как вы видите, код очень компактный:
В этом примере серверный код не представлен, он за рамками этой статьи, он принимает POST-запрос с данными формы и отвечает сообщением «Пользователь сохранён».
Методы объекта FormData
С помощью указанных ниже методов мы можем изменять поля в объекте FormData :
Поля объекта formData можно перебирать, используя цикл for..of :
Отправка формы с файлом
Пример такой формы:
Отправка формы с Blob-данными
Но на практике бывает удобнее отправлять изображение не отдельно, а в составе формы, добавив дополнительные поля для имени и другие метаданные.
Кроме того, серверы часто настроены на приём именно форм, а не просто бинарных данных.
В примере ниже посылается изображение из и ещё несколько полей, как форма, используя FormData :
Пожалуйста, обратите внимание на то, как добавляется изображение Blob :
Это как если бы в форме был элемент и пользователь прикрепил бы файл с именем «image.png» (3й аргумент) и данными imageBlob (2й аргумент) из своей файловой системы.
Сервер прочитает и данные и файл, точно так же, как если бы это была обычная отправка формы.
Итого
Объекты FormData используются, чтобы взять данные из HTML-формы и отправить их с помощью fetch или другого метода для работы с сетью.
Отметим две особенности:

