Введение
Использование веб-сервисов платформы 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 + Выборка.ИмяФайла + Выборка.РасширениеФайла;
ОбъединитьФайлы(МассивИменФайловДляОбъединения, ИмяРезультатирующегоФайла);
Попытка
УдалитьФайлы(ВременныйКаталог, "*");
Исключение КонецПопытки;
КонецЕсли;
Возврат ИмяРезультатирующегоФайла;
КонецФункции
Вот и все, такая простая реализация! Посмотрим на результат.
Проверка
Передача файла была продемонстрирована выше, теперь же давайте посмотрим на работоспособность функции получения ранее переданного файла.
Как видим, исходный файл получен с тем же размером. Задача выполнена!
Выводы
При регулярном использовании веб-сервиса для передачи больших по размеру сообщений наиболее предпочтителен второй вариант решения, который позволяет сделать универсальную и надежную передачу сообщений вне зависимости от настроек веб-сервера и исходного размера файла.
Предложенный механизм передачи больших сообщений можно развивать, добавляя проверки корректной отправки сообщений, автоматическую очистку битых пакетов из регистра сведений, очистку устаревших данных из регистра и др. Был показан лишь принцип, остальное дело за Вами!