При интеграции 1С со сторонними веб-сервисами (через REST API, SOAP или HTTP-запросы) разработчики часто сталкиваются с ситуацией, когда в теле ответа вместо ссылки на скачивание или прямого бинарного потока возвращается гигантская текстовая строка. Попытка просто сохранить такую строку в текстовый файл с расширением .pdf приводит к тому, что документ не открывается ни одной программой для чтения PDF.
Разберем причину этого явления: веб-сервисы передают бинарные файлы внутри JSON, XML или текстовых ответов в виде строки, закодированной по алгоритму Base64. Выясним, как правильно декодировать такие данные встроенными средствами платформы 1С:Предприятие, как корректно сохранить их на диск, а также как обработать возможные ошибки и передать сформированный файл на сторону клиента.
Для обратного преобразования закодированной строки в исходные бинарные данные платформа 1С предоставляет глобальную функцию Base64Значение(). На вход функция принимает исходную строку, а возвращает объект типа ДвоичныеДанные. Полученные двоичные данные можно сохранить в файл нужного расширения с помощью метода Записать().
Рассмотрим простейший пример кода:
// Входящая переменная СтрокаBase64 содержит тело файла ответа сервиса
ИмяФайла = "C:\Temp\Документ.pdf";
// Преобразуем строку Base64 в двоичные данные
ДвоичныеДанныеФайла = Base64Значение(СтрокаBase64);
// Записываем двоичные данные на диск
ДвоичныеДанныеФайла.Записать(ИмяФайла);
Этот код отлично работает в стандартных ситуациях, однако на практике интеграция часто требует учета ряда технических нюансов: нормализации данных, проверки формата и корректного разделения серверного и клиентского контекста.
Проанализируем типовые проблемы, из-за которых функция Base64Значение() может завершиться аварийно или сформировать поврежденный файл:
data:application/pdf;base64,JVBERi0x.... Этот заголовок не является частью полезных данных алгоритма Base64. Если передать его в Base64Значение(), платформа не сможет корректно распознать байты. Префикс обязательно нужно отсекать до запятой.Символы.ПС и Символы.ВК. Их рекомендуется предварительно удалять.+ заменяется на -, а / на _), стандартный декодер 1С вернет ошибку. В таком случае необходимо выполнить обратную замену символов и дополнить строку знаками = в конце, чтобы её общая длина была кратна 4.Напишем универсальную функцию очистки строки перед её декодированием:
Функция ОчиститьСтрокуBase64(Знач ВходящаяСтрока)
// 1. Удаляем префикс Data URI, если он присутствует
ПозицияЗапятой = СтрНайти(ВходящаяСтрока, ",");
Если ПозицияЗапятой > 0 И СтрНайти(НСтр(ВходящаяСтрока), "data:") > 0 Тогда
ВходящаяСтрока = Сред(ВходящаяСтрока, ПозицияЗапятой + 1);
КонецЕсли;
// 2. Убираем переносы строк и пробелы
ВходящаяСтрока = СтрЗаменить(ВходящаяСтрока, Символы.ПС, "");
ВходящаяСтрока = СтрЗаменить(ВходящаяСтрока, Символы.ВК, "");
ВходящаяСтрока = СтрЗаменить(ВходящаяСтрока, " ", "");
// 3. Обрабатываем URL-safe Base64 при необходимости
ВходящаяСтрока = СтрЗаменить(ВходящаяСтрока, "-", "+");
ВходящаяСтрока = СтрЗаменить(ВходящаяСтрока, "_", "/");
ОстатокОтДеления = СтрДлина(ВходящаяСтрока) % 4;
Если ОстатокОтДеления > 0 Тогда
ВходящаяСтрока = ВходящаяСтрока + СтрШаблон("%1", ?(ОстатокОтДеления = 2, "==", "="));
КонецЕсли;
Возврат ВходящаяСтрока;
КонецФункции
Прежде чем сохранять файл или открывать его пользователю, полезно убедиться, что веб-сервис прислал именно PDF, а не текст ошибки или пустой блок. Любой стандартный PDF-документ начинается со специфической сигнатуры — магических байтов %PDF-.
Посмотрим на пример проверки сигнатуры через объект ЧтениеДанных:
Функция ЭтоКорректныйPDF(ДвоичныеДанныеФайла)
Если ДвоичныеДанныеФайла = Неопределено Тогда
Возврат Ложь;
КонецЕсли;
Поток = ДвоичныеДанныеФайла.ОткрытьПотокДляЧтения();
Чтение = Новый ЧтениеДанных(Поток);
// Считываем первые 5 байт
Буфер = Чтение.ПрочитатьВБуферДвоичныхДанных(5);
Чтение.Закрыть();
СтрокаСигнатуры = ПолучитьСтрокуИзБуфераДвоичныхДанных(Буфер, КодировкаТекста.ANSI);
// Проверяем соответствие заголовку %PDF-
Возврат (СтрокаСигнатуры = "%PDF-");
КонецФункции
В реальных проектах на управляемых формах вызов веб-сервиса и декодирование происходят на стороне сервера. Если вызвать ДвоичныеДанные.Записать() на сервере, файл будет сохранен в файловой системе сервера 1С:Предприятия, и клиентский компьютер пользователя не получит к нему доступа.
Разберем правильный алгоритм передачи файла:
Base64Значение().ДвоичныеДанные во временное хранилище с помощью метода ПоместитьВоВременноеХранилище().ПолучитьИмяВременногоФайла("pdf") и метод ПолучитьФайлИзВременногоХранилища().ЗапуститьПриложение().Посмотрим на реализацию в коде формы:
&НаСервере
Функция ПолучитьФайлPDFНаСервере()
// Имитация получения данных из сервиса
СтрокаBase64 = ПолучитьОтветВебСервиса();
СтрокаBase64 = ОчиститьСтрокуBase64(СтрокаBase64);
ДвоичныеДанныеPDF = Base64Значение(СтрокаBase64);
Если Не ЭтоКорректныйPDF(ДвоичныеДанныеPDF) Тогда
ВызватьИсключение "Сервер вернул поврежденный или неверный формат PDF!";
КонецЕсли;
// Помещаем во временное хранилище и возвращаем адрес на клиент
Возврат ПоместитьВоВременноеХранилище(ДвоичныеДанныеPDF, УникальныйИдентификатор);
КонецФункции
&НаКлиенте
Процедура ОткрытьПолученныйPDF(Команда)
АдресХранилища = ПолучитьФайлPDFНаСервере();
Если ЗначениеЗаполнено(АдресХранилища) Тогда
ПутьКФайлу = ПолучитьИмяВременногоФайла("pdf");
// Получаем файл из хранилища на клиентский диск
ПолучитьФайлИзВременногоХранилища(АдресХранилища, ПутьКФайлу);
// Открываем документ стандартным средством операционной системы
ЗапуститьПриложение(ПутьКФайлу);
КонецЕсли;
КонецПроцедуры
Обратите внимание на важную особенность платформы 1С: строки в оперативной памяти хранятся в кодировке UTF-16, то есть каждый символ занимает 2 байта. Сам по себе алгоритм Base64 увеличивает объем исходного файла примерно на 33%. Таким образом, обработка PDF-файла размером в 50 Мб в виде строки Base64 может потребовать от 150 до 200 Мб оперативной памяти только на поддержание переменной в контексте.
Если веб-сервер позволяет отдавать данные без сериализации в JSON/XML, а напрямую в бинарном формате application/pdf, предпочтительнее выполнять HTTP-запрос через объект HTTPСоединение и сохранять ответ напрямую на диск методом HTTPОтвет.ПолучитьТелоКакИмяФайла(). Это полностью исключит загрузку тяжелых строк в оперативную память сервера.