gifts2017

Передача больших пакетов через веб-сервисы

Опубликовал Юрий Пермитин (YPermitin) в раздел Программирование - Практика программирования

Реализация механизма передачи больших пакетов через веб-сервисы. С его помощью передать файл размером в несколько гигабайт не составит проблем.

Введение

Использование веб-сервисов платформы 1С:Предприятие набирает обороты для решения задач интеграции: обмены данными между базами, взаимодействие с мобильными или веб-приложениями и многое другое. Огромные возможности могут покрыть любые потребности при решении задач интеграции. Если Вы разрабатываете веб-сервисы для интеграции больших / высоконагруженных баз, то возможно описанная здесь проблема будет уже не новой.

Сегодня мы поговорим о передаче больших по размеру пакетов через веб-сервис, об ограничении веб-сервера и способе решения сложившейся ситуации. Суть проблемы заключается в следующем: стандартная конфигурация веб-сервера (будь то это IIS или Apache) содержат настройки по ограничению максимального размера пакета, который может быть обработан. Для IIS максимальный размер обрабатываемого сообщения ~30 МБ, а для Apache ~16 МБ. На счет Apache могу ошибаться, т.к. при установках стандартные настройки были разными.

При создании обменов данными через веб-сервисы размер отправляемого сообщения может быть значительно больше заданных ограничений. Например, при выгрузке из УПП 1.3 документа распределения косвенных расходов размер сформированного XML-файла в сжатом виде может достигать пару сотен мегабайт! В этом случае обмен просто встанет и сервер не сможет обработать входящее сообщение.

Рассмотрим два способа решения данной проблемы: с помощью настроек веб-сервера (на примере IIS) и с помощью разработанного механизма передачи сообщения по частям.

Быстрое решение

Если максимальный размер входящего сообщения нужно увеличить незначительно, то можно использовать быстрое решение, а именно изменить конфигурацию веб-сервера для обработки пакетов большего размера. Далее продемонстрировано изменение настроек для веб-сервера IIS несколькими способами:

 

1. Настройка через диспетчер служб IIS:

 

2. Изменение файла "web.config" в корне директории веб-приложения:


<system.webServer> 
   <security> 
      <requestFiltering> 
         <requestLimits maxAllowedContentLength="1048576000" /> 
      </requestFiltering> 
   </security> 
</system.webServer>


3. В командной строке выполнить:

cd c:\Windows\System32\inetsrv

appcmd set config "Default Web Site" -section:requestFiltering -requestLimits.maxAllowedContentLength:1048576000 -commitpath:apphost

Правильное решение
В случаях, когда размер передаваемого пакета значительно больше установленных по умолчанию ограничений, изменять конфигурацию веб-сервера не рекомендую. Если заставить принимать веб-сервер пакеты в несколько гигабайт, то это может значительно повлиять на его производительность / работоспособность. Лучше всего сделать передачу большого пакета частями. Далее рассмотрим простейшую реализацию такого механизма на платформе 1С:Предприятие с использованием веб-сервисов.


Реализация
В тестовой конфигурации сделан пример веб-сервиса для передачи пакетов частями. Общий принцип следующий: через веб-сервис передаются части файла и записываются в регистр сведений. Для всех частей файла присваивается некоторый GUID, по которому файл можно будет "склеить" обратно, а также порядковый номер части. Наглядно передачу файла размером в 170 МБ по частям с размером 5 МБ можно представить так:

Для промежуточного сохранения частей файла используется регистр сведений. Передача файла через веб-сервис выполняется с использованием XDTO-пакетов следующей структуры:

Фактически пакет дублирует структуру регистра сведений:

Далее представлен обработчик метода веб-сервиса:

Функция executeMethod(MessagePart)
	
	Ответ = ФабрикаXDTO.Создать(ФабрикаXDTO.Пакеты.Получить("http://www.develplatform.ru").Получить("MessagePartResponse"));
	
	Попытка
		РегистрыСведений.ПринятыеЧастиПакета.ЗафиксироватьПриемЧастиПакета(
			Новый УникальныйИдентификатор(MessagePart.MessageId),
			MessagePart.PartNumber,
			MessagePart.PartData,
			MessagePart.CountOfParts,
			MessagePart.MessageName,
			MessagePart.FileExtention,
			MessagePart.FileName,
			MessagePart.Size
		);
		Ответ.Success = Истина;
	Исключение
		Ответ.Success = Ложь;	
	КонецПопытки;
	
	Возврат Ответ;
	
КонецФункции

В качестве параметра метод веб-сервиса принимает объект с типом "MessagePartRequest" и передает из него данные в функцию "ЗафиксироватьПриемПакета". Эта функция сохраняет полученные через веб-сервис данные в базу:

Процедура ЗафиксироватьПриемЧастиПакета(Идентификатор, НомерЧасти, Данные, ВсегоЧастей, ИмяСообщения, РасширениеФайла, ИмяФайла, Размер) Экспорт
	
	Набор = РегистрыСведений.ПринятыеЧастиПакета.СоздатьНаборЗаписей();
	Набор.Отбор.ИдентификаторПакета.Установить(Идентификатор);
	Набор.Отбор.НомерЧасти.Установить(НомерЧасти);
	Запись = Набор.Добавить();
	Запись.ДанныеЧастиСообщения = Новый ХранилищеЗначения(Данные);
	Запись.ИдентификаторПакета = Идентификатор;
	Запись.НомерЧасти = НомерЧасти;
	Запись.ДатаСоздания = ТекущаяДата();
	Запись.ВсегоЧастей = ВсегоЧастей;
	Запись.ИмяСообщения = ИмяСообщения;
	Запись.РасширениеФайла = РасширениеФайла;
	Запись.ИмяФайла = ИмяФайла;
	Запись.РазмерФайла = Размер;
	Набор.Записать();
	
КонецПроцедуры

Также в конфигурацию добавлен общий модуль ОбменДаннымиWS с функциями отправки и получения файла. Функция отправки файла разбивает его на части и передает каждую часть отдельно, обращаясь к веб-сервису:

// Отправляет указанный файл на сервер через веб-сервис
//	Параметры:
//		1. ПутьКФайлуНаСервере - строка. Путь к передаваемому файлы на сервере
//		2. МаксимальныйРазмерЧастиПакетаБайт - число. Максимальный размер одной передаваемой части в байтах
//			По умолчанию 10 МБ.
//	Возвращаемое значение:
//		Уникальный идентификатор отправленного файла
//
Функция ОтправитьФайл(ПутьКФайлуНаСервере, МаксимальныйРазмерЧастиПакетаБайт = 10485760) Экспорт
	
	Slash = Символ(92); // Символ "/"
	ИдентификаторСообщения = Новый УникальныйИдентификатор;
	
	// Создаем временный каталог для сохранения в него частей исходного файла
	ВременныйКаталог = КаталогВременныхФайлов()+"SendingMessageWS"+Slash+ИдентификаторСообщения;
	СоздатьКаталог(ВременныйКаталог);
	// Разбиваем файл на части с помощью возможностей платформы
	РазделитьФайл(ПутьКФайлуНаСервере, МаксимальныйРазмерЧастиПакетаБайт, ВременныйКаталог);
	ВсеНайденныеФайлы = НайтиФайлы(ВременныйКаталог, "*");
	ИсходныйФайл = Новый Файл(ПутьКФайлуНаСервере);
	
	// Каждую часть файла отправляем через веб-сервис
	НомерЧасти = 1;
	Для Каждого Эл Из ВсеНайденныеФайлы Цикл
		Прокси = WSСсылки.SendMessageParts.СоздатьWSПрокси("http://www.develplatform.ru/SendBigMessage", "DevelPlatformRU", "DevelPlatformRUSoap");
		ТипОбъектаЗапроса = Прокси.ФабрикаXDTO.Пакеты.Получить("http://www.develplatform.ru").Получить("MessagePartRequest");
		ОбъектЗапроса = Прокси.ФабрикаXDTO.Создать(ТипОбъектаЗапроса);
		ОбъектЗапроса.MessageId = Строка(ИдентификаторСообщения);
		ОбъектЗапроса.PartNumber = НомерЧасти;
		ОбъектЗапроса.PartData = Новый ДвоичныеДанные(Эл.ПолноеИмя);
		ОбъектЗапроса.CountOfParts = ВсеНайденныеФайлы.Количество();
		ОбъектЗапроса.MessageName = "Тестовая отправка сообщения!";
		ОбъектЗапроса.FileExtention = ИсходныйФайл.Расширение;
		ОбъектЗапроса.FileName = ИсходныйФайл.ИмяБезРасширения;
		ОбъектЗапроса.Size = Эл.Размер();
		Результат = Прокси.execute(ОбъектЗапроса);
		
		НомерЧасти = НомерЧасти + 1;
	КонецЦикла;
	
	Попытка
		УдалитьФайлы(ВременныйКаталог, "*");
	Исключение КонецПопытки;
	
	Возврат ИдентификаторСообщения;
	
КонецФункции

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

// Отправляет указанный файл на сервер через веб-сервис
//	Параметры:
//		1. ИдентификаторСообщения - Уникальный идентификатор. Идентификатор, возвращенный функцией "ОтправитьФайл"
//	Возвращаемое значение:
//		Строка. Путь к собранному файлу на сервере
//
Функция ПолучитьФайл(ИдентификаторСообщения) Экспорт
	
	Slash = Символ(92); // Символ "/"
		
	// Создаем временный каталог для записи в него сохраненных ранее в базе частей
	КаталогВременныхФайлов = КаталогВременныхФайлов() +"ReceivingMessageWS";
	ВременныйКаталог = КаталогВременныхФайлов + Slash + ИдентификаторСообщения;
	СоздатьКаталог(ВременныйКаталог);
	ИмяРезультатирующегоФайла = Неопределено;
	
	// Получаем все сохраненные части в базе
	Запрос = Новый Запрос;
	Запрос.Текст = 
		"ВЫБРАТЬ
		|	ПринятыеЧастиПакета.ИдентификаторПакета,
		|	ПринятыеЧастиПакета.НомерЧасти,
		|	ПринятыеЧастиПакета.ДанныеЧастиСообщения,
		|	ПринятыеЧастиПакета.ДатаСоздания,
		|	ПринятыеЧастиПакета.ВсегоЧастей,
		|	ПринятыеЧастиПакета.ИмяСообщения,
		|	ПринятыеЧастиПакета.РасширениеФайла,
		|	ПринятыеЧастиПакета.ИмяФайла,
		|	ПринятыеЧастиПакета.РазмерФайла
		|ИЗ
		|	РегистрСведений.ПринятыеЧастиПакета КАК ПринятыеЧастиПакета
		|ГДЕ
		|	ПринятыеЧастиПакета.ИдентификаторПакета = &ИдентификаторПакета";	
	Запрос.УстановитьПараметр("ИдентификаторПакета", ИдентификаторСообщения);	
	РезультатЗапроса = Запрос.Выполнить();
	Если НЕ РезультатЗапроса.Пустой() Тогда
		Выборка = РезультатЗапроса.Выбрать();
		
		МассивИменФайловДляОбъединения = Новый Массив;

		// Сохраняем файлы частей во временный каталог
		Пока Выборка.Следующий() Цикл
			ИмяЧастиФайла = ВременныйКаталог + Slash + Выборка.ИмяФайла + Выборка.РасширениеФайла + "." + Формат(Выборка.НомерЧасти, "ЧГ=0");
			Выборка.ДанныеЧастиСообщения.Получить().Записать(ИмяЧастиФайла);
			МассивИменФайловДляОбъединения.Добавить(ИмяЧастиФайла);
		КонецЦикла;
		// Собираем исходный файл
		ИмяРезультатирующегоФайла = КаталогВременныхФайлов + Slash + Выборка.ИмяФайла + Выборка.РасширениеФайла;
		ОбъединитьФайлы(МассивИменФайловДляОбъединения, ИмяРезультатирующегоФайла); 
		
		Попытка
			УдалитьФайлы(ВременныйКаталог, "*");
		Исключение КонецПопытки;
		
	КонецЕсли;
	
	Возврат ИмяРезультатирующегоФайла;
	
КонецФункции

Вот и все, такая простая реализация! Посмотрим на результат.

Проверка

Передача файла была продемонстрирована выше, теперь же давайте посмотрим на работоспособность функции получения ранее переданного файла.

Как видим, исходный файл получен с тем же размером. Задача выполнена!

Выводы

При регулярном использовании веб-сервиса для передачи больших по размеру сообщений наиболее предпочтителен второй вариант решения, который позволяет сделать универсальную и надежную передачу сообщений вне зависимости от настроек веб-сервера и исходного размера файла.

Предложенный механизм передачи больших сообщений можно развивать, добавляя проверки корректной отправки сообщений, автоматическую очистку битых пакетов из регистра сведений, очистку устаревших данных из регистра и др. Был показан лишь принцип, остальное дело за Вами!

 

P.S. Оригинал статьи и другие материалы по интеграции через веб-сервисы, использование HTTP-сервисов, создание асинхронных виджетов и многое другое Вы можете найти в моем блоге www.develplatform.ru

Скачать файлы

Наименование Файл Версия Размер
Передача больших пакетов через веб-сервисы частями 19
.cf 50,78Kb
05.12.15
19
.cf 0.1 50,78Kb Скачать

См. также

Подписаться Добавить вознаграждение

Комментарии

1. script Мальчинко (script) 06.12.15 09:40
Данная методика давно уже используется во многих типовых решения для обмена с мобильным клиентом. Только регистр там называется ОчередиСообщений. Ну нужно сказать что деление файла на части хоть и позволяет передать много данных, но существенно тормозит процесс обмена. Все как всегда, что быстрее работать с одним большим файлом или с кучей мелких.
2. Юрий Пермитин (YPermitin) 06.12.15 10:11
(1) script, спасибо за информацию. Там это реализовано немного сложнее. Но принцип, согласен, тот же.
3. Дмитрий Шерстобитов (DitriX) 06.12.15 15:25
А еще до мобильной - это есть все в бсп :)
Но есть и другое решение - вам надо с УТП забрать файл в 1Гб, и поместить его, например, в документооборот, не важно.
Действия такие - документооборот подключается к УТП (возможно по инициализации самой УТП) и забирает оттуда файл в 1Гб без ограничений :)
4. Юрий Пермитин (YPermitin) 06.12.15 16:36
(3) DitriX, только не говорите, что он подключается по COM =)
5. bonv (bonv) 07.12.15 11:59
В первую очередь, говоря про передачу больших файлов, следует отметить, что использование SOAP имеет накладные расходы в виде увеличения объема передаваемых данных на 33%.
Лучше использовать голый HTTP.
6. bonv (bonv) 07.12.15 12:03
(0)
Slash = Символ(92); // Символ "/"
....
ВременныйКаталог = КаталогВременныхФайлов()+"SendingMessageWS"+Slash+ИдентификаторСообщения;

Черт, вы это серьезно? Разделитель задаете через это Символ(92)?
vtas; YPermitin; +2 Ответить 1
7. Олег Филиппов (comol) 07.12.15 14:05
Круто! Вопрос только один: А нафига зачем??? Одно дело упростить себе жизнь и использовать SOAP для построения событийной модели обмена... Другое дело лить через http протокол файлы. Ну и выгружайте тогда уж в файл, притом лучше не XML, а хотя бы FI тогда уж... а по SOAP передавайте ссылку на него...
YPermitin; skif47; Mick2iS; +3 Ответить 2
8. Юрий Пермитин (YPermitin) 07.12.15 22:12
(6) bonv, Это не то, что вы подумали! =D
9. Юрий Пермитин (YPermitin) 07.12.15 22:13
(5) bonv, согласен, можно и JSON использовать, уже будет легче. Совершенству нет предела)
10. Юрий Пермитин (YPermitin) 09.12.15 08:49
(7) comol, сразу забыл ответить, сорри)
Так то оно так, но если переход на обмен через веб-сервисы выполняется со старого транспорта, который выполнял выгрузку в XML, то время на изменение обмена может очень дорого стоить компании. Компромиссный вариант - изменить вид транспорта и немного механизмы обработки сообщений, вместо переписки основной части выгрузки и загрузки данных.
11. Данила Володькин (skif47) 09.12.15 08:59
В одной своей разработке тоже уткнулся в относительно большие размеры сообщений, передаваемых через SOAP (в моем случае 7-8 МБ).
Загрузка с тестового сервера Амазон во Франкфурте не прошла: отвалилось по таймауту.
Стал на стороне сервера сжимать ответ сервера в ZIP и передавать его тем же SOAP как base64Binary (http://www.w3.org/2001/XMLSchema). Средний размер файла оказался уже около 500 КБ. Распаковка в XML и парсинг в XDTO объект проблем не вызвала, поскольку сам объект WSProxy предоставляет заодно и фабрику XDTO, которой уже можно парсить разжатые ответы.
Протестировал то же самое со сжатием FastInfoSet вместо XML - выигрыш получился незначительный.
Если вместо SOAP использовать HTTP, тоже будет некоторый выигрыш.

А идеально (в моем случае, по крайней мере) использовать рецепт из (7), причем даже асинхронный. Т.е. сервер сразу же возвращает URL файла. А клиент периодически проверяет наличие этого файла, и, как только тот стал доступен, начинает скачивать. Возможно, уже с разбивкой на пакеты.

В любом случае за тему плюс ))
12. Дмитрий К. (Dementor) 09.12.15 11:35
Идея интересная, если делать тиражное решение для абстрактного клиента, на инфраструктуру которого не возможно повлиять.
Но на своем опыте могу сказать - при задаче частого обмена большими файлами проще в веб-сервере убрать ограничение для опубликованной базы.
13. Дмитрий К. (Dementor) 09.12.15 12:00
(5) bonv, если использовать бинарный тип (hexBinary), то накладные расходы SOAP будут всего лишь в добавлении envelope-обвязки пакета - можно пренебречь. Сам к сожалению не экспериментировал, так как мне были доступны для передачи файлы кодированные в Base64 (как раз эти самые потери 33% на увеличении объема), но если возможность в платформе есть, то и имеется вероятность отличная от нуля, что это работает.

При передаче файлов POST-ом по протоколу HTTP та же петрушка, что и при SOAP - данные часто передают типом application/x-www-form-urlencoded
14. Максим *** (premier) 09.12.15 12:37
(0) А я вот не понял смысла промежуточной записи файлов в регистр сведений. Почему сразу в каталог с неким GUID их не писать, а имена коротких файлов для дальнейшего объединения запоминать в массиве?
15. Юрий Пермитин (YPermitin) 09.12.15 13:10
(12) Dementor, админы негодуют, когда просишь убрать ограничения. В принципе я с ними согласен)
16. Юрий Пермитин (YPermitin) 09.12.15 13:18
(13) Dementor, Вы правы, сам SOAPовский конверт имеет незначительный вес, а вот преобразование в base64 увеличивает размер передаваемых данных на треть.

(14) premier, проще отслеживать состояние передачи пакета, можно запросами платформы получать состояния передачи пакетов и т.д., а с каталогом пришлось бы для этого делать "извраты" с чтением файлов и т.д. Сами данные пакета не обязательно записывать в регистр сведений, можно в нем хранить лишь путь к файлу части пакета на диске (что-то вроде хранения файлов в томах). На рабочей базе лучше реализовать периодическую очистку этого регистра, чтобы старые пакеты в нем не хранились долго.

Вариантов реализации масса)
17. bonv (bonv) 10.12.15 07:56
(13) срочно учить матчасть!
если использовать бинарный тип (hexBinary), то накладные расходы SOAP будут всего лишь в добавлении envelope-обвязки пакета - можно пренебречь

Если использовать hexBinary, то расходы будут 100%. hexBinary использует для отображения одного байта 2 символа.

А вот MTOM в платформе еще пока нет. Так что только голый HTTP.

При передаче файлов POST-ом по протоколу HTTP та же петрушка, что и при SOAP - данные часто передают типом application/x-www-form-urlencoded

А application/x-www-form-urlencoded то тут причем. Данный формат предназначен для передачи параметров веб-формы. Для передачи бинарных данных никто его в здравом уме использовать не будет.
18. Иван Иванов (rail21111991) 30.12.15 13:54
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа