В данной небольшой статье рассмотрены вопросы формирования двоичных данных в multipart формате, чтения multipart сообщения, а также приведен пример работы с заголовками для http-сервисов при формировании multipart сообщений.
Немного теории о формате multipart
Multipart – это формат составных сообщений, используемый при обмене данными по протоколу HTTP. У этого формата множество подтипов, каждый из которых служит для разных целей. Например, один из самых распространенных подтипов – multipart/form-data – используется чаще всего для отправки HTML-форм с вложенными двоичными данными, например электронных писем с вложениями. А multipart/alternative используется для представления одной и той же информации в разных формах. Например, один и тот же документ передается в виде плоского текста, rtf и в формате docx.
Как формировать файл в формате multipart в 1С?
Сперва рассмотрим основную механику формирования multipart данных. Минимально необходимо для этого – иметь собственно несколько блоков данных и разделитель. Допустим, мы хотим получить двоичные данные из нескольких объединенных файлов и записать их в один файл.
Приведенный ниже код делает следующее: На клиенте мы даем пользователю выбрать файлы, после чего передаем их на сервер. На сервере мы формируем двоичные данные, используя разделитель – в нашем примере это уникальный идентификатор, но можно использовать и любую другую строку из допустимых символов. Подробнее описано в стандарте RFC 2046. Разделители между частями начинаются с символов равно “==”. Разделители, кроме последнего пишутся перед соответствующей частью. Последний разделитель имеет отличие – он и начинается и заканчивается двумя символами равно “==”. Полученные двоичные данные передаются обратно на клиент, и пользователю предоставляется возможность в диалоге указать место сохранения итогового файла.
&НаСервере Функция СформироватьСообщениеНаСервере(МассивПомещенныхФайлов) Разделитель = Новый УникальныйИдентификатор; Тело = Новый ПотокВПамяти(); ЗаписьДанных = Новый ЗаписьДанных(Тело); Для Каждого ПомещенныйФайл Из МассивПомещенныхФайлов Цикл ДД = ПолучитьИзВременногоХранилища(ПомещенныйФайл.Адрес); ЗаписьДанных.ЗаписатьСтроку("==" + Разделитель); ЗаписьДанных.Записать(ДД); КонецЦикла; ЗаписьДанных.Закрыть(); ЗаписьДанных.ЗаписатьСтроку("==" + Разделитель + "=="); ДанныеТела = Тело.ЗакрытьИПолучитьДвоичныеДанные(); АдресВХ = ПоместитьВоВременноеХранилище(ДанныеТела); Возврат Новый Структура("Адрес, ИмяФайла, Разделитель", АдресВХ, , Разделитель); КонецФункции &НаКлиенте Асинх Процедура СформироватьСообщение(Команда) МассивПомещенныхФайлов = Ждать ПоместитьФайлыНаСерверАсинх(,,Новый ПараметрыДиалогаПомещенияФайлов()); ПомещенныеФайлы = Новый Массив; Для Каждого ПомещенныйФайл Из МассивПомещенныхФайлов Цикл СтруктураПомещенногоФайла = Новый Структура; СтруктураПомещенногоФайла.Вставить("Адрес", ПомещенныйФайл.Адрес); СтруктураПомещенногоФайла.Вставить("ИмяФайла", ПомещенныйФайл.СсылкаНаФайл.Имя); ПомещенныеФайлы.Добавить(СтруктураПомещенногоФайла); КонецЦикла; СтруктураВозврата = СформироватьСообщениеНаСервере(ПомещенныеФайлы); Сообщить(СтруктураВозврата.Разделитель); Ждать ПолучитьФайлССервераАсинх(СтруктураВозврата.Адрес, , Новый ПараметрыДиалогаПолученияФайлов()); КонецПроцедуры
Как в 1С прочитать файл с multipart содержимым?
Вариант 1 - как предлагает ИТС, с текстовыми разделителями
Данный вариант предполагает последовательное чтение данных с разделением по маркерам-разделителям. Для наглядности добавлено сообщение о времени начала и времени окончания выполнения кода. Чем больше разбираемый файл, тем дольше будет выполняться процедура чтения. Распознанные двоичные данные отдельных частей записываются в отдельные файлы, также для наглядности. Разумеется, в реальных сценариях их использование может отличаться – например, их можно записать в хранилище значений в СУБД, или передать в какой-нибудь http-запрос в качестве тела, и т.д.
&НаСервере Процедура ПрочитатьСообщениеПервыйВариантНаСервере(Адрес) Тело = ПолучитьИзВременногоХранилища(Адрес); Маркеры = Новый Массив(); Маркеры.Добавить("==" + Разделитель); Маркеры.Добавить("==" + Разделитель + Символы.ПС); Маркеры.Добавить("==" + Разделитель + Символы.ВК); Маркеры.Добавить("==" + Разделитель + Символы.ВК + Символы.ПС); Маркеры.Добавить("==" + Разделитель + "=="); ЧтениеДанных = Новый ЧтениеДанных(Тело); // Переходим к началу первой части ЧтениеДанных.ПропуститьДо(Маркеры); // Далее в цикле читаем все части СтруктураРаспознанногоСообщения = Новый Структура; Сч = 1; Пока Истина Цикл Часть = чтениеДанных.ПрочитатьДо(Маркеры); Если Не Часть.МаркерНайден Тогда // Неправильно сформированное сообщение Прервать; КонецЕсли; ЧтениеЧасти = Новый ЧтениеДанных(Часть.ОткрытьПотокДляЧтения()); СтруктураРаспознанногоСообщения.Вставить("Файл" + Сч, ЧтениеЧасти.Прочитать().ПолучитьДвоичныеДанные()); Сч = Сч + 1; Если Часть.ИндексМаркера = 4 Тогда // Прочитали последнюю часть Прервать; КонецЕсли; КонецЦикла; Для Каждого ДД Из СтруктураРаспознанногоСообщения Цикл ДД.Значение.Записать("E:\Путь к файлу\" + ДД.Ключ); КонецЦикла; КонецПроцедуры &НаКлиенте Асинх Процедура ПрочитатьСообщениеПервыйВариант(Команда) Результат = Ждать ПоместитьФайлНаСерверАсинх(,,, Новый ПараметрыДиалогаПомещенияФайлов()); Сообщить(ТекущаяДата()); ПрочитатьСообщениеПервыйВариантНаСервере(Результат.Адрес); Сообщить(ТекущаяДата()); КонецПроцедуры
Вариант 2 - быстрый, с разделителями-буферами двоичных данных
Второй вариант кратно быстрее первого. В нем вместо текстовых разделителей используются буферы двоичных данных. В этом случае последовательного чтения не требуется, мы сразуполучаем массив буферов двоичных данных с нашими сообщениями. Единственный нюанс – может получиться так, что после разделения по маркерам в буфере ДД будет пусто, и в этом случае его нужно пропустить.
&НаСервере Процедура ПрочитатьСообщениеВторойВариантНаСервере(Адрес) Тело = ПолучитьИзВременногоХранилища(Адрес); Маркеры = Новый Массив(); Маркеры.Добавить(ПолучитьБуферДвоичныхДанныхИзСтроки("==" + Разделитель)); Маркеры.Добавить(ПолучитьБуферДвоичныхДанныхИзСтроки("==" + Разделитель + Символы.ПС)); Маркеры.Добавить(ПолучитьБуферДвоичныхДанныхИзСтроки("==" + Разделитель + Символы.ВК)); Маркеры.Добавить(ПолучитьБуферДвоичныхДанныхИзСтроки("==" + Разделитель + Символы.ВК + Символы.ПС)); Маркеры.Добавить(ПолучитьБуферДвоичныхДанныхИзСтроки("==" + Разделитель + "==")); ЧтениеДанных = Новый ЧтениеДанных(Тело); ЧтениеДанных.ПропуститьДо(Маркеры); ОбщийБуферДвоичныхДанных = ЧтениеДанных.ПрочитатьВБуферДвоичныхДанных(); БуферыДвоичныхДанных = ОбщийБуферДвоичныхДанных.Разделить(Маркеры); СтруктураРаспознанногоСообщения = Новый Структура; Сч = 1; Для Каждого Буфер Из БуферыДвоичныхДанных Цикл Поток = новый ПотокВПамяти(Буфер); ЧтениеЧасти = Новый ЧтениеДанных(Поток); ДД = ЧтениеЧасти.Прочитать().ПолучитьДвоичныеДанные(); Если ДД.Размер() = 0 Тогда Продолжить; КонецЕсли; СтруктураРаспознанногоСообщения.Вставить("ФайлВариантДва" + Сч, ДД); Сч = Сч + 1; ЧтениеЧасти.Закрыть(); Поток.Закрыть(); КонецЦикла; Для Каждого ДД Из СтруктураРаспознанногоСообщения Цикл ДД.Значение.Записать("E:\Путь к файлу\" + ДД.Ключ); КонецЦикла; КонецПроцедуры
Как сформировать сообщение multipart/form-data в 1C?
Предположим, мы хотим средствами языка 1С сформировать HTTP запрос с передачей сообщения в формате multipart/form-data. В этом случае мы должны формировать наше составное сообщение определенным образом. После строки-разделителя мы должны добавить строку с указанием имени нижеследующей части, строку с типом данных этой части, и пустую строку. И только после этого записывать сами данные. Например:
ЗаписьДанных.ЗаписатьСтроку("==" + Разделитель); ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=" + ПомещенныйФайл.ИмяФайла + "; filename=" + ПомещенныйФайл.ИмяФайла); ЗаписьДанных.ЗаписатьСтроку("Content-Type: application/octet-stream"); ЗаписьДанных.ЗаписатьСтроку(""); ЗаписьДанных.Записать(ДД);
Кроме того, при формировании HTTP-запроса мы должны указать в заголовке тип сообщения “multipart/form-data” и наш разделитель. Подробнее по работе с POST-запросами можно прочитать тут.
Результат.Вставить("Заголовки", Заголовки); Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + разделитель);
Как прочитать сообщение multipart/form-data в 1C?
Рассмотрим случай, когда мы разрабатываем HTTP-сервис для POST–запросов, тело которых приходит в формате multipart/form-data. Для разбора сообщения нам нужно знать разделитель, его мы можем получить, проанализировав заголовки входящего HTTP-запроса. Кроме того, у каждой части также будут свои заголовки, а именно – Content-Disposition: form-data, и Content-Type. Пример их формирования см. выше. Проанализировав их, мы сможем узнать тип содержимого данной части, имя этой части, имя файла (если данная часть содержит именно файл). И в зависимости от этого уже реализовывать свою логику – например, у нас может быть часть с JSON, из которой мы создадим элемент справочника, часть – текст, который мы запишем в регистр логирования, и часть – двоичные данные, которые мы прикрепим к созданному элементу справочника в виде присоединенного файла. Так что универсального кода, который будет работать с любой структурой сообщения, не бывает – все зависит от конкретной архитектуры вашего решения, и прикладной логики разбора входящих сообщений. Один из вариантов реализации приведен на сайте ИТС: https://its.1c.ru/db/metod8dev#content:5917:hdoc:http-messages
Там можно ознакомиться с примером разбора заголовка Content-Type для получения разделителя, а также изучить разбор Content-Disposition для получения имени части. Либо можно написать свой код для парсинга, например при помощи регулярных выражений.
А зная разделитель, вы можете разобрать multipart-сообщение, как было показано выше – с использованием текстовых маркеров или буферов двоичных данных.