Как правильно сохранить PDF-файл, полученный через HTTP-запрос в 1С, если он не открывается после сохранения

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Часто при интеграции 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, считают файл поврежденным в целях безопасности. Браузеры же работают более "лояльно", просто игнорируя всё, что находится за пределами финального маркера.

Диагностика содержимого через текстовое представление и HEX

Чтобы понять, что именно мешает файлу открыться, проанализируем его содержимое. Рассмотрим два способа проверки в среде 1С.

Способ 1. Проверка через ПолучитьТелоКакСтроку(). Хотя этот метод категорически не рекомендуется для сохранения бинарных данных, он полезен для быстрой отладки, чтобы увидеть текстовые маркеры внутри PDF. Посмотрим на начало и конец полученных данных:


ТекстДляАнализа = HTTPОтвет.ПолучитьТелоКакСтроку();
// В начале мы должны увидеть: %PDF-1.7
// В самом конце мы должны увидеть: %%EOF

Если после %%EOF вы видите строку вида --bec04134-9487-484c..., значит, сервер отдает вам данные в формате multipart или при загрузке файла на сервер произошла ошибка склейки данных. Именно эти лишние 30–40 байт "ломают" файл.

Способ 2. Использование HEX-редактора. Это самый надежный метод. Откроем сохраненный файл в любом HEX-редакторе. Если в самом конце файла после байтов 25 25 45 4F 46 (что соответствует %%EOF) идут какие-то другие значения — их нужно удалять.

Причины возникновения лишних данных

Выясним причину появления мусора в конце файла. Рассмотрим основные варианты:

  1. Ошибка при загрузке файла (Multipart/form-data): Если вы сами ранее загружали этот файл на сервис через POST-запрос, вы могли допустить ошибку в формировании тела запроса. В 1С при ручной сборке multipart-запроса часто добавляют лишний перевод строки или забывают, что закрывающий boundary должен иметь два дефиса в конце и за ним не должно быть лишних байтов, которые могут "приклеиться" к полезной нагрузке.
  2. Некорректные заголовки сервера: Сервер может передавать файл, упакованный в обертку, которую 1С воспринимает как часть бинарного потока.
  3. Проблемы кодировки: Никогда не используйте ПолучитьТелоКакСтроку() с последующей записью через ТекстовыйДокумент. Это гарантированно исказит бинарные данные, так как символы, не попадающие в таблицу кодировки (например, UTF-8), будут заменены на символы-заглушки.

Решение: Программная очистка PDF-файла в 1С

Если вы не можете изменить поведение сервера, мы можем программно "откусить" лишние байты в 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");
КонецЕсли;

Проверка заголовков Accept и Content-Type

Посмотрим на важность правильных заголовков. В вашем HTTPЗапрос желательно явно указывать, какой тип контента вы ожидаете получить.

Заголовок Accept: Сообщает серверу, что мы хотим получить именно PDF. Если сервер поддерживает версионность контента, он отправит чистый бинарный поток. Попробуйте установить:

Заголовки.Вставить("Accept", "application/pdf");

Заголовок Content-Type в ответе: Проанализируйте, что возвращает сервер. Если там указано multipart/form-data, то наличие boundary в теле — это стандартное поведение, и вам необходимо использовать парсинг тела запроса, чтобы извлечь конкретную "часть" (part) с файлом.

Резюме

Для успешного сохранения PDF в 1С придерживайтесь следующих правил:

  1. Используйте только ДвоичныеДанные или Потоки для сохранения. Забудьте про текстовые строки.
  2. Если файл поврежден — проверьте его в HEX-редакторе на наличие лишних байтов после %%EOF.
  3. При обнаружении лишних данных исправьте логику на стороне сервера (загрузки) или используйте ЧтениеДанных в 1С для обрезки потока.
  4. Для работы с большими файлами всегда указывайте ИмяВыходногоФайла в методе Получить().

Соблюдение этих технических нюансов позволит вашей интеграции работать стабильно, а документы будут открываться в любых приложениях без ошибок.

← На главную