Введение
Использование веб-сервисов платформы 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 + Выборка.ИмяФайла + Выборка.РасширениеФайла;
ОбъединитьФайлы(МассивИменФайловДляОбъединения, ИмяРезультатирующегоФайла);
Попытка
УдалитьФайлы(ВременныйКаталог, "*");
Исключение КонецПопытки;
КонецЕсли;
Возврат ИмяРезультатирующегоФайла;
КонецФункции
Вот и все, такая простая реализация! Посмотрим на результат.
Проверка
Передача файла была продемонстрирована выше, теперь же давайте посмотрим на работоспособность функции получения ранее переданного файла.
Как видим, исходный файл получен с тем же размером. Задача выполнена!
Выводы
При регулярном использовании веб-сервиса для передачи больших по размеру сообщений наиболее предпочтителен второй вариант решения, который позволяет сделать универсальную и надежную передачу сообщений вне зависимости от настроек веб-сервера и исходного размера файла.
Предложенный механизм передачи больших сообщений можно развивать, добавляя проверки корректной отправки сообщений, автоматическую очистку битых пакетов из регистра сведений, очистку устаревших данных из регистра и др. Был показан лишь принцип, остальное дело за Вами!
Данная методика давно уже используется во многих типовых решения для обмена с мобильным клиентом. Только регистр там называется ОчередиСообщений. Ну нужно сказать что деление файла на части хоть и позволяет передать много данных, но существенно тормозит процесс обмена. Все как всегда, что быстрее работать с одним большим файлом или с кучей мелких.
(1) script, спасибо за информацию. Там это реализовано немного сложнее. Но принцип, согласен, тот же.
А еще до мобильной — это есть все в бсп 🙂
Но есть и другое решение — вам надо с УТП забрать файл в 1Гб, и поместить его, например, в документооборот, не важно.
Действия такие — документооборот подключается к УТП (возможно по инициализации самой УТП) и забирает оттуда файл в 1Гб без ограничений 🙂
(3) DitriX, только не говорите, что он подключается по COM =)
В первую очередь, говоря про передачу больших файлов, следует отметить, что использование SOAP имеет накладные расходы в виде увеличения объема передаваемых данных на 33%.
Лучше использовать голый HTTP.
(0)
….
ВременныйКаталог = КаталогВременныхФайлов()+»SendingMessageWS»+Slash+ИдентификаторСообщения;
Черт, вы это серьезно? Разделитель задаете через это Символ(92)?
Круто! Вопрос только один: А
нафигазачем??? Одно дело упростить себе жизнь и использовать SOAP для построения событийной модели обмена… Другое дело лить через http протокол файлы. Ну и выгружайте тогда уж в файл, притом лучше не XML, а хотя бы FI тогда уж… а по SOAP передавайте ссылку на него…(6) bonv, Это не то, что вы подумали! =D
(5) bonv, согласен, можно и JSON использовать, уже будет легче. Совершенству нет предела)
(7) comol, сразу забыл ответить, сорри)
Так то оно так, но если переход на обмен через веб-сервисы выполняется со старого транспорта, который выполнял выгрузку в XML, то время на изменение обмена может очень дорого стоить компании. Компромиссный вариант — изменить вид транспорта и немного механизмы обработки сообщений, вместо переписки основной части выгрузки и загрузки данных.
В одной своей разработке тоже уткнулся в относительно большие размеры сообщений, передаваемых через SOAP (в моем случае 7-8 МБ).
http://www.w3.org/2001/XMLSchema) . Средний размер файла оказался уже около 500 КБ. Распаковка в XML и парсинг в XDTO объект проблем не вызвала, поскольку сам объект WSProxy предоставляет заодно и фабрику XDTO, которой уже можно парсить разжатые ответы.
Загрузка с тестового сервера Амазон во Франкфурте не прошла: отвалилось по таймауту.
Стал на стороне сервера сжимать ответ сервера в ZIP и передавать его тем же SOAP как base64Binary (
Протестировал то же самое со сжатием FastInfoSet вместо XML — выигрыш получился незначительный.
Если вместо SOAP использовать HTTP, тоже будет некоторый выигрыш.
А идеально (в моем случае, по крайней мере) использовать рецепт из (7), причем даже асинхронный. Т.е. сервер сразу же возвращает URL файла. А клиент периодически проверяет наличие этого файла, и, как только тот стал доступен, начинает скачивать. Возможно, уже с разбивкой на пакеты.
В любом случае за тему плюс ))
Идея интересная, если делать тиражное решение для абстрактного клиента, на инфраструктуру которого не возможно повлиять.
Но на своем опыте могу сказать — при задаче частого обмена большими файлами проще в веб-сервере убрать ограничение для опубликованной базы.
(5) bonv, если использовать бинарный тип (hexBinary), то накладные расходы SOAP будут всего лишь в добавлении envelope-обвязки пакета — можно пренебречь. Сам к сожалению не экспериментировал, так как мне были доступны для передачи файлы кодированные в Base64 (как раз эти самые потери 33% на увеличении объема), но если возможность в платформе есть, то и имеется вероятность отличная от нуля, что это работает.
При передаче файлов POST-ом по протоколу HTTP та же петрушка, что и при SOAP — данные часто передают типом application/x-www-form-urlencoded
(0) А я вот не понял смысла промежуточной записи файлов в регистр сведений. Почему сразу в каталог с неким GUID их не писать, а имена коротких файлов для дальнейшего объединения запоминать в массиве?
(12) Dementor, админы негодуют, когда просишь убрать ограничения. В принципе я с ними согласен)
(13) Dementor, Вы правы, сам SOAPовский конверт имеет незначительный вес, а вот преобразование в base64 увеличивает размер передаваемых данных на треть.
(14) premier, проще отслеживать состояние передачи пакета, можно запросами платформы получать состояния передачи пакетов и т.д., а с каталогом пришлось бы для этого делать «извраты» с чтением файлов и т.д. Сами данные пакета не обязательно записывать в регистр сведений, можно в нем хранить лишь путь к файлу части пакета на диске (что-то вроде хранения файлов в томах). На рабочей базе лучше реализовать периодическую очистку этого регистра, чтобы старые пакеты в нем не хранились долго.
Вариантов реализации масса)
(13) срочно учить матчасть!
Если использовать hexBinary, то расходы будут 100%. hexBinary использует для отображения одного байта 2 символа.
А вотMTOM в платформе еще пока нет. Так что только голый HTTP.
А application/x-www-form-urlencoded то тут причем. Данный формат предназначен для передачи параметров веб-формы. Для передачи бинарных данных никто его в здравом уме использовать не будет.
123