Часто при интеграции 1С с внешними веб-сервисами программисты сталкиваются с ситуацией: файл (например, PDF) успешно получается через HTTPСоединение, сохраняется на диск, имеет адекватный размер, но при попытке открытия через Adobe Reader выдает ошибку о повреждении структуры. При этом браузеры (например, Chrome) могут открывать этот же файл без проблем. В этой статье мы подробно разберем, почему это происходит, как диагностировать проблему и какими методами в 1С можно очистить бинарный поток от "мусора".
Рассмотрим типовую ситуацию. Мы выполняем GET-запрос к API, получаем тело ответа и записываем его на диск. Код выглядит примерно так:
Соединение = Новый HTTPСоединение("api.service.kz", 443, , , , , Новый ЗащищенноеСоединениеOpenSSL());
Запрос = Новый HTTPЗапрос(АдресРесурса, Заголовки);
HTTPОтвет = Соединение.Получить(Запрос);
Если HTTPОтвет.КодСостояния = 200 Тогда
ДвоичныеДанныеФайла = HTTPОтвет.ПолучитьТелоКакДвоичныеДанные();
ДвоичныеДанныеФайла.Записать("D:\temp\document.pdf");
КонецЕсли;
Файл создан, размер совпадает с ожидаемым, но специализированные программы для чтения PDF сообщают о критической ошибке. Проанализируем причину такой избирательности просмотрщиков. Дело в том, что стандарт PDF очень строг к структуре файла. PDF-документ должен начинаться с определенного заголовка (Magic Number) %PDF- и обязательно заканчиваться маркером конца файла %%EOF. Если после этого маркера в файле присутствуют любые лишние байты (пробелы, переносы строк или остатки разделителей boundary), профессиональные продукты, такие как Adobe Acrobat, считают файл поврежденным в целях безопасности. Браузеры же работают более "лояльно", просто игнорируя всё, что находится за пределами финального маркера.
Чтобы понять, что именно мешает файлу открыться, проанализируем его содержимое. Рассмотрим два способа проверки в среде 1С.
Способ 1. Проверка через ПолучитьТелоКакСтроку(). Хотя этот метод категорически не рекомендуется для сохранения бинарных данных, он полезен для быстрой отладки, чтобы увидеть текстовые маркеры внутри PDF. Посмотрим на начало и конец полученных данных:
ТекстДляАнализа = HTTPОтвет.ПолучитьТелоКакСтроку();
// В начале мы должны увидеть: %PDF-1.7
// В самом конце мы должны увидеть: %%EOF
Если после %%EOF вы видите строку вида --bec04134-9487-484c..., значит, сервер отдает вам данные в формате multipart или при загрузке файла на сервер произошла ошибка склейки данных. Именно эти лишние 30–40 байт "ломают" файл.
Способ 2. Использование HEX-редактора. Это самый надежный метод. Откроем сохраненный файл в любом HEX-редакторе. Если в самом конце файла после байтов 25 25 45 4F 46 (что соответствует %%EOF) идут какие-то другие значения — их нужно удалять.
Выясним причину появления мусора в конце файла. Рассмотрим основные варианты:
multipart-запроса часто добавляют лишний перевод строки или забывают, что закрывающий boundary должен иметь два дефиса в конце и за ним не должно быть лишних байтов, которые могут "приклеиться" к полезной нагрузке.ПолучитьТелоКакСтроку() с последующей записью через ТекстовыйДокумент. Это гарантированно исказит бинарные данные, так как символы, не попадающие в таблицу кодировки (например, UTF-8), будут заменены на символы-заглушки.Если вы не можете изменить поведение сервера, мы можем программно "откусить" лишние байты в 1С, используя объекты для работы с потоками. Рассмотрим, как это сделать эффективно.
Начиная с версии 8.3.10, в 1С реализованы мощные инструменты для работы с бинарными данными. Воспользуемся объектами ЧтениеДанных и ЗаписьДанных.
// Предположим, мы уже получили HTTPОтвет
ДвоичныеДанные = HTTPОтвет.ПолучитьТелоКакДвоичныеДанные();
РазмерФайла = ДвоичныеДанные.Размер();
// Читаем данные в поток для анализа
Поток = ДвоичныеДанные.ОткрытьПотокДляЧтения();
Чтение = Новый ЧтениеДанных(Поток);
// PDF читается с конца, но мы можем найти позицию маркера %%EOF
// Для простоты примера, если мы знаем, что лишних байт ровно 38:
НужныйРазмер = РазмерФайла - 38;
Поток.Перейти(0, ПозицияВПотоке.Начало);
РезультатБинарный = Чтение.Прочитать(НужныйРазмер);
// Сохраняем очищенные данные
РезультатБинарный.Записать("D:\3_fixed.pdf");
Поток.Закрыть();
Проанализируем более сложный и универсальный подход. Мы можем динамически искать вхождение %%EOF с конца файла и обрезать всё лишнее. Это гарантирует, что файл будет валидным для любого строгого просмотрщика.
Разберем еще один важный момент. При работе с большими PDF-файлами (более 50 Мб) метод HTTPОтвет.ПолучитьТелоКакДвоичныеДанные() может привести к чрезмерному потреблению оперативной памяти, так как 1С сначала загружает весь ответ в память, а затем создает объект ДвоичныеДанные.
Для оптимизации рекомендуется использовать возможность записи ответа напрямую в файл. Рассмотрим этот метод:
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("pdf");
// Соединение запишет поток сразу на диск, минуя оперативную память
HTTPОтвет = Соединение.Получить(Запрос, ИмяВременногоФайла);
Если HTTPОтвет.КодСостояния = 200 Тогда
// Здесь можно выполнить проверку размера или очистку, если она нужна
ПереместитьФайл(ИмяВременногоФайла, "D:\ИтоговыйФайл.pdf");
КонецЕсли;
Посмотрим на важность правильных заголовков. В вашем HTTPЗапрос желательно явно указывать, какой тип контента вы ожидаете получить.
Заголовок Accept: Сообщает серверу, что мы хотим получить именно PDF. Если сервер поддерживает версионность контента, он отправит чистый бинарный поток. Попробуйте установить:
Заголовки.Вставить("Accept", "application/pdf");
Заголовок Content-Type в ответе: Проанализируйте, что возвращает сервер. Если там указано multipart/form-data, то наличие boundary в теле — это стандартное поведение, и вам необходимо использовать парсинг тела запроса, чтобы извлечь конкретную "часть" (part) с файлом.
Для успешного сохранения PDF в 1С придерживайтесь следующих правил:
ЧтениеДанных в 1С для обрезки потока.Получить().Соблюдение этих технических нюансов позволит вашей интеграции работать стабильно, а документы будут открываться в любых приложениях без ошибок.