Получить консультацию

Работа с multipart-сообщениями в 1С

В данной небольшой статье рассмотрены вопросы формирования двоичных данных в 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-сообщение, как было показано выше – с использованием текстовых маркеров или буферов двоичных данных.

2 комментария к “Работа с multipart-сообщениями в 1С”

  1. Владимир

    Принятые файлы xlsx по 2 методу оказались поврежденными (так говорит Excel при их открытии), но если согласиться на восстановление (предлагает Excel), то файл открывается.
    По какой причине так происходит, не подскажете?

    1. Могу только предположить, что возможно, неверно считается какая-нибудь контрольная сумма или хэш, либо заголовки. Как вариант – взять “нормальный” файл, взять “поврежденный” и сравнить двоичные данные в hex-редакторе. Еще вариант – разложить файл на составляющие (офисные документы по сути – запакованные архивы с кучей мелких файликов), и поанализировать их – либо двоичные данные, либо посравнивать тексты.

Оставьте комментарий

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

два + три =

К НАЧАЛУ