Как правильно записывать двоичные данные в поток в 1С и избежать обрезания файлов?

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

При разработке интеграций, например, при отправке файлов в Telegram через бота или взаимодействии с внешними API, программисты часто сталкиваются с ситуацией, когда передаваемый файл оказывается "битым" или неполным. Одной из самых коварных проблем является внезапное обрезание файла на определенном байте, хотя визуально код кажется абсолютно корректным.

В этой статье мы подробно разберем, почему стандартные методы работы со строками не подходят для передачи файлов, как устроены потоки в 1С и как правильно сформировать тело HTTP-запроса, чтобы данные доходили до адресата в неизменном виде.

Анализ проблемы: почему ЗаписатьСтроку портит данные?

Рассмотрим типичную ситуацию. Программист пытается сформировать Multipart/form-data запрос вручную, используя ПотокВПамяти и объект ЗаписьДанных. Код выглядит следующим образом:


Объект.ДД2 = Новый ДвоичныеДанные(Объект.ПутьКФайлу);
Тело = Новый ПотокВПамяти();
ЗаписьДанных = Новый ЗаписьДанных(Тело);
// ... формирование заголовков ...
ЗаписьДанных.ЗаписатьСтроку(Объект.ДД2); // Ошибка здесь

В данном примере автор столкнулся с тем, что файл весом в несколько мегабайт на стороне сервера превращался в огрызок размером 770 байт. Давайте выясним причину. Метод ЗаписатьСтроку предназначен исключительно для работы с текстовой информацией. Когда в этот метод передается объект типа ДвоичныеДанные, платформа 1С выполняет неявное преобразование типов.

Во-первых, срабатывает логика "нулевого байта". В низкоуровневых языках программирования, на которых построена платформа, строки часто обрабатываются до первого символа с кодом 0x00 (null-terminator). Если в вашем бинарном файле (картинке, архиве, PDF) на 771-й позиции встретился байт со значением 0, функция преобразования решит, что строка закончилась, и все последующие данные будут проигнорированы.

Во-вторых, кодировка текста. Метод ЗаписатьСтроку всегда работает в контексте определенной кодировки (обычно UTF-8). Бинарные данные — это не текст, а произвольный набор байтов. Платформа попытается интерпретировать этот набор как символы. Если последовательность байтов не соответствует правилам UTF-8, данные будут либо искажены, либо заменены на символы-заполнители, что сделает файл нечитаемым.

Решение проблемы: правильный метод записи

Для корректной передачи содержимого файла необходимо использовать метод, который работает с данными "как есть", без попыток интерпретировать их как текст. Таким методом является Записать. Давайте проанализируем исправленный фрагмент кода:


// Вместо ЗаписатьСтроку используем прямой метод записи двоичных данных
ЗаписьДанных.Записать(Объект.ДД2);

Метод Записать объекта ЗаписьДанных принимает на вход ДвоичныеДанные и помещает их в поток байт за байтом, не обращая внимания на наличие нулей или специальных символов. Именно это позволяет передавать файлы любого формата без потерь.

Тонкости формирования Multipart/form-data

Когда мы формируем тело запроса для Telegram Bot API или других веб-сервисов, важно соблюдать стандарт HTTP. Одной замены метода записи недостаточно для стабильной работы. Рассмотрим важные нюансы, которые часто упускают из виду:

  1. Разделители строк (CRLF): Согласно спецификации, части multipart-запроса должны разделяться последовательностью \r\n. В 1С это Символы.ВК + Символы.ПС. Если ваша база работает на сервере под управлением Linux, использование стандартного метода ЗаписатьСтроку() без параметров может добавить только LF (перевод строки), что приведет к ошибке 400 (Bad Request) от сервера.
  2. Маркер BOM (Byte Order Mark): По умолчанию ЗаписьДанных может добавлять в начало потока три невидимых байта (BOM для UTF-8). Для текстовых файлов это нормально, но при формировании тела HTTP-запроса это может "свести с ума" веб-сервер. Чтобы этого избежать, при создании объекта ЗаписьДанных следует явно указывать параметры кодировки без BOM.
  3. Завершение записи: После того как все данные записаны, необходимо вызвать метод Закрыть() для объекта ЗаписьДанных. Это гарантирует, что все данные из внутреннего буфера будут принудительно "вытолкнуты" в ПотокВПамяти. Если этого не сделать, хвост вашего сообщения может потеряться.

Оптимизация: работаем с памятью правильно

Рассмотрим ситуацию, когда нам нужно отправить файл размером 100-200 Мб. Использование метода Новый ДвоичныеДанные(ПутьКФайлу) загрузит весь файл в оперативную память. Затем, при записи в ПотокВПамяти, данные будут продублированы. Это может привести к критической ошибке "Недостаточно памяти".

Давайте разберем более профессиональный подход с использованием файловых потоков, который позволяет передавать данные порциями, не занимая лишнюю память:


// Открываем поток для чтения из файла на диске
ФайловыйПоток = Новый ФайловыйПоток(Объект.ПутьКФайлу, РежимОткрытияФайла.Открыть, ДоступКФайлу.Чтение);

// Формируем текстовую часть заголовка
ЗаписьДанных.ЗаписатьСтроку("--" + Разделитель);
ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""document""; filename=""file.pdf""");
ЗаписьДанных.ЗаписатьСтроку("Content-Type: application/octet-stream");
ЗаписьДанных.ЗаписатьСтроку(""); // Пустая строка перед данными
ЗаписьДанных.Закрыть(); // Сбрасываем буфер заголовков в основной поток

// Копируем данные из файла напрямую в поток тела запроса
КопироватьДанные(ФайловыйПоток, Тело);

// Снова открываем запись для финального разделителя
ЗаписьДанныхКонец = Новый ЗаписьДанных(Тело);
ЗаписьДанныхКонец.ЗаписатьСтроку("");
ЗаписьДанныхКонец.ЗаписатьСтроку("--" + Разделитель + "--");
ЗаписьДанныхКонец.Закрыть();

В этом примере функция КопироватьДанные эффективно перекачивает байты из файла в память, используя небольшой буфер. Это позволяет обрабатывать файлы практически любого размера.

Итоговый алгоритм действий

Для того чтобы ваша передача данных работала стабильно, придерживайтесь следующих правил:

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

← На главную