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

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

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

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

20 − пятнадцать =

К НАЧАЛУ