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






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

Введение

Использование веб-сервисов платформы 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:WindowsSystem32inetsrv

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

18 Comments

  1. script

    Данная методика давно уже используется во многих типовых решения для обмена с мобильным клиентом. Только регистр там называется ОчередиСообщений. Ну нужно сказать что деление файла на части хоть и позволяет передать много данных, но существенно тормозит процесс обмена. Все как всегда, что быстрее работать с одним большим файлом или с кучей мелких.

    Reply
  2. YPermitin

    (1) script, спасибо за информацию. Там это реализовано немного сложнее. Но принцип, согласен, тот же.

    Reply
  3. DitriX

    А еще до мобильной — это есть все в бсп 🙂

    Но есть и другое решение — вам надо с УТП забрать файл в 1Гб, и поместить его, например, в документооборот, не важно.

    Действия такие — документооборот подключается к УТП (возможно по инициализации самой УТП) и забирает оттуда файл в 1Гб без ограничений 🙂

    Reply
  4. YPermitin

    (3) DitriX, только не говорите, что он подключается по COM =)

    Reply
  5. bonv

    В первую очередь, говоря про передачу больших файлов, следует отметить, что использование SOAP имеет накладные расходы в виде увеличения объема передаваемых данных на 33%.

    Лучше использовать голый HTTP.

    Reply
  6. bonv

    (0)

    Slash = Символ(92); // Символ «/»

    ….

    ВременныйКаталог = КаталогВременныхФайлов()+»SendingMessageWS»+Slash+ИдентификаторСообщения;

    Черт, вы это серьезно? Разделитель задаете через это Символ(92)?

    Reply
  7. comol

    Круто! Вопрос только один: А нафига зачем??? Одно дело упростить себе жизнь и использовать SOAP для построения событийной модели обмена… Другое дело лить через http протокол файлы. Ну и выгружайте тогда уж в файл, притом лучше не XML, а хотя бы FI тогда уж… а по SOAP передавайте ссылку на него…

    Reply
  8. YPermitin

    (6) bonv, Это не то, что вы подумали! =D

    Reply
  9. YPermitin

    (5) bonv, согласен, можно и JSON использовать, уже будет легче. Совершенству нет предела)

    Reply
  10. YPermitin

    (7) comol, сразу забыл ответить, сорри)

    Так то оно так, но если переход на обмен через веб-сервисы выполняется со старого транспорта, который выполнял выгрузку в XML, то время на изменение обмена может очень дорого стоить компании. Компромиссный вариант — изменить вид транспорта и немного механизмы обработки сообщений, вместо переписки основной части выгрузки и загрузки данных.

    Reply
  11. skif47

    В одной своей разработке тоже уткнулся в относительно большие размеры сообщений, передаваемых через SOAP (в моем случае 7-8 МБ).

    Загрузка с тестового сервера Амазон во Франкфурте не прошла: отвалилось по таймауту.

    Стал на стороне сервера сжимать ответ сервера в ZIP и передавать его тем же SOAP как base64Binary (http://www.w3.org/2001/XMLSchema). Средний размер файла оказался уже около 500 КБ. Распаковка в XML и парсинг в XDTO объект проблем не вызвала, поскольку сам объект WSProxy предоставляет заодно и фабрику XDTO, которой уже можно парсить разжатые ответы.

    Протестировал то же самое со сжатием FastInfoSet вместо XML — выигрыш получился незначительный.

    Если вместо SOAP использовать HTTP, тоже будет некоторый выигрыш.

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

    В любом случае за тему плюс ))

    Reply
  12. Dementor

    Идея интересная, если делать тиражное решение для абстрактного клиента, на инфраструктуру которого не возможно повлиять.

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

    Reply
  13. Dementor

    (5) bonv, если использовать бинарный тип (hexBinary), то накладные расходы SOAP будут всего лишь в добавлении envelope-обвязки пакета — можно пренебречь. Сам к сожалению не экспериментировал, так как мне были доступны для передачи файлы кодированные в Base64 (как раз эти самые потери 33% на увеличении объема), но если возможность в платформе есть, то и имеется вероятность отличная от нуля, что это работает.

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

    Reply
  14. premierex

    (0) А я вот не понял смысла промежуточной записи файлов в регистр сведений. Почему сразу в каталог с неким GUID их не писать, а имена коротких файлов для дальнейшего объединения запоминать в массиве?

    Reply
  15. YPermitin

    (12) Dementor, админы негодуют, когда просишь убрать ограничения. В принципе я с ними согласен)

    Reply
  16. YPermitin

    (13) Dementor, Вы правы, сам SOAPовский конверт имеет незначительный вес, а вот преобразование в base64 увеличивает размер передаваемых данных на треть.

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

    Вариантов реализации масса)

    Reply
  17. bonv

    (13) срочно учить матчасть!

    если использовать бинарный тип (hexBinary), то накладные расходы SOAP будут всего лишь в добавлении envelope-обвязки пакета — можно пренебречь

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

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

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

    А application/x-www-form-urlencoded то тут причем. Данный формат предназначен для передачи параметров веб-формы. Для передачи бинарных данных никто его в здравом уме использовать не будет.

    Reply
  18. rail21111991

    123

    Reply

Leave a Comment

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