При интеграции систем «1С:Предприятие» со сторонними веб-сервисами и мессенджерами (например, при отправке документов или изображений в Telegram, работе с REST API различных облачных сервисов) разработчики часто сталкиваются с необходимостью передачи бинарных данных. Исторически сложилось так, что для этого использовалась схема с сохранением файла на жесткий диск во временный каталог, последующим его чтением и отправкой. Однако такой подход имеет множество недостатков: лишние операции ввода-вывода (I/O) снижают общую производительность, возникают проблемы с правами доступа к файловой системе на сервере, а также возрастает риск захламления диска временными файлами при аварийном завершении сеанса. Для отправки сформированных отчетов в мессенджер в виде картинок можно использовать обработку для отправки табличного документа в Telegram в виде фото.
В этой статье мы подробно разберем, как полностью отказаться от использования временных файлов на диске при работе с картинками и другими двоичными данными. Мы изучим современные механизмы платформы «1С:Предприятие 8.3», позволяющие выполнять все операции перекодирования и сборки HTTP-запросов непосредственно в оперативной памяти.
В процессе разработки у многих возникает соблазн представить бинарный файл (картинку) в виде обычной строки в кодировке ANSI (или любой другой однобайтовой кодировке) для последующей вставки в тело текстового запроса. Это фундаментальное заблуждение, которое гарантированно приводит к повреждению передаваемых данных.
Выясним причину, почему это происходит. Любой графический файл (будь то JPEG, PNG или GIF) состоит из последовательности байтов, которая может принимать любые значения от 0x00 до 0xFF. Текстовые кодировки предназначены для интерпретации этих байтов как символов. Если мы попытаемся прочитать двоичные данные как текст, мы столкнемся со следующими критическими проблемами:
0x00 во многих системных библиотеках и механизмах интерпретируется как признак окончания строки. При попытке преобразовать бинарный поток в строку чтение может аварийно прерваться на первом же нулевом байте, из-за чего файл будет прочитан не полностью.? или спецсимвол U+FFFD). Обратное преобразование такой строки в двоичные данные вернет уже поврежденный файл, который ни одна программа не сможет открыть как картинку.Важное правило разработки: Бинарные данные должны передаваться и обрабатываться исключительно в виде двоичных данных или потоков. Преобразование в строку допускается только с использованием специализированных алгоритмов безопасного текстового кодирования, таких как Base64, если это явно требуется принимающим API (например, при передаче картинки внутри структуры JSON).
Для понимания процессов кодирования важно помнить, что внутри платформы «1С:Предприятие 8» все строковые данные хранятся исключительно в формате Unicode (UTF-16). Это позволяет платформе одновременно поддерживать символы различных языков мира в пределах одной информационной базы.
Когда мы передаем строку во внешнюю систему (например, по HTTP), нам почти всегда требуется преобразовать её из внутреннего UTF-16 в UTF-8 или национальную кодировку (например, Windows-1251 для ANSI-совместимых систем). В современных версиях платформы это делается «на лету» в памяти без записи промежуточных текстовых файлов.
Начиная с версии платформы 8.3.10.2168, разработчикам стали доступны удобные глобальные функции для быстрого прямого преобразования данных в памяти, которые существенно упростили жизнь:
ПолучитьСтрокуИзДвоичныхДанных() — позволяет мгновенно декодировать двоичные данные в строку с указанием целевой кодировки.ПолучитьДвоичныеДанныеИзСтроки() — выполняет обратную операцию, преобразуя текст в бинарный буфер заданной кодировки.Base64Строка() и Base64Значение() — стандартные функции для кодирования двоичных данных в безопасный текстовый формат Base64.Рассмотрим простейший пример, как преобразовать строку в кодировку Windows-1251 (ANSI) в памяти и получить её двоичный эквивалент:
ИсходнаяСтрока = "Привет, мир!";
ДвоичныеДанныеANSI = ПолучитьДвоичныеДанныеИзСтроки(ИсходнаяСтрока, КодировкаТекста.ANSI, Ложь);
// Теперь ДвоичныеДанныеANSI содержат байты строки в однобайтовой кодировке Windows-1251
Если пользователь выбрал картинку на клиенте или мы получили её из базы данных, нам нет необходимости записывать её на диск, чтобы передать в HTTP-запрос. У объекта Картинка в 1С есть замечательный встроенный метод, возвращающий двоичные данные напрямую:
// Предположим, КартинкаОбъект содержит нашу картинку
ДвоичныеДанныеФайла = КартинкаОбъект.ПолучитьДвоичныеДанные();
Если же картинка хранится в базе данных в реквизите с типом ХранилищеЗначения, мы извлекаем её аналогичным образом:
ДвоичныеДанныеФайла = СсылкаНаОбъект.ХранилищеКартинки.Получить();
// Проверим тип полученного объекта
Если ТипЗнч(ДвоичныеДанныеФайла) = Тип("Картинка") Тогда
ДвоичныеДанныеФайла = ДвоичныеДанныеФайла.ПолучитьДвоичныеДанные();
КонецЕсли;
Теперь, имея в оперативной памяти объект типа ДвоичныеДанные, мы готовы собрать тело нашего POST-запроса.
Формат запроса multipart/form-data используется для одновременной передачи текстовых полей (например, идентификатора чата chat_id) и бинарных файлов. Этот формат требует строгого разделения полей специальной строкой-разделителем — boundary.
Основная сложность заключается в том, что тело запроса должно содержать как текстовые заголовки (в кодировке UTF-8), так и сырые бинарные байты картинки. Использование обычного текстового документа здесь невозможно, так как он повредит картинку. Решение — использовать объект ПотокВПамяти совместно с объектом ЗаписьДанных.
Давайте подробно разберем по шагам, как реализовать этот алгоритм:
Шаг 1. Инициализация потока и объекта записи. Мы создаем поток в оперативной памяти и связываем его с объектом записи. В настройках ЗаписьДанных мы явно указываем кодировку UTF-8 без вывода маркера BOM (Byte Order Mark), чтобы веб-сервер корректно обработал заголовки.
Шаг 2. Формирование текстовых полей. Записываем разделители и метаданные текстовых параметров. Каждый параметр отделяется пустой строкой.
Шаг 3. Запись бинарного файла. Записываем заголовок секции файла, а затем передаем непосредственно двоичные данные нашей картинки методом Записать() объекта ЗаписьДанных. Платформа запишет «сырые» байты картинки прямо в наш поток без какого-либо искажения.
Шаг 4. Закрытие потока и получение результата. Завершаем формирование запроса финальным разделителем, закрываем поток и извлекаем готовые двоичные данные для HTTP-запроса.
Посмотрим на практическую реализацию этого метода в коде:
Функция СформироватьТелоЗапросаДляTelegram(Токен, ЧатID, ДвоичныеДанныеКартинки) Экспорт
Boundary = Строка(Новый УникальныйИдентификатор);
// Создаем поток в оперативной памяти
Поток = Новый ПотокВПамяти;
// Создаем ЗаписьДанных, привязанную к потоку. Отключаем BOM!
ЗаписьДанных = Новый ЗаписьДанных(Поток, КодировкаТекста.UTF8, , Ложь);
// --- Поле "chat_id" ---
ЗаписьДанных.ЗаписатьСтроку("--" + Boundary);
ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""chat_id""");
ЗаписьДанных.ЗаписатьСтроку(""); // Пустая строка - разделитель заголовка и значения по стандарту HTTP
ЗаписьДанных.ЗаписатьСтроку(Строка(ЧатID));
// --- Поле "photo" (наша картинка) ---
ЗаписьДанных.ЗаписатьСтроку("--" + Boundary);
ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""photo""; filename=""image.jpg""");
ЗаписьДанных.ЗаписатьСтроку("Content-Type: image/jpeg");
ЗаписьДанных.ЗаписатьСтроку(""); // Отделяем заголовки секции от самого бинарного контента
// Записываем Двоичные Данные картинки прямо в поток БЕЗ конвертации в строку!
ЗаписьДанных.Записать(ДвоичныеДанныеКартинки);
// Закрываем multipart структуру
ЗаписьДанных.ЗаписатьСтроку("");
ЗаписьДанных.ЗаписатьСтроку("--" + Boundary + "--");
// Сбрасываем буфер записи в поток
ЗаписьДанных.Закрыть();
// Получаем результирующие двоичные данные из потока памяти
РезультирующиеДанные = Поток.ЗакрытьИПолучитьДвоичныеДанные();
Возврат Новый Структура("Тело, Boundary", РезультирующиеДанные, Boundary);
КонецФункции
Теперь, когда тело нашего запроса полностью подготовлено в памяти компьютера, мы можем отправить его с помощью стандартного объекта HTTPСоединение. Нам нужно лишь правильно установить заголовки запроса, указав наш уникальный Boundary.
Рассмотрим пример отправки подготовленных данных в Telegram:
Процедура ОтправитьКартинкуВТелеграм(Токен, ЧатID, КартинкаОбъект)
// Извлекаем двоичные данные картинки из объекта
ДвоичныеДанныеКартинки = КартинкаОбъект.ПолучитьДвоичныеДанные();
// Формируем тело запроса в оперативной памяти
ПараметрыЗапроса = СформироватьТелоЗапросаДляTelegram(Токен, ЧатID, ДвоичныеДанныеКартинки);
// Настраиваем HTTP-запрос
HTTPЗапрос = Новый HTTPЗапрос;
HTTPЗапрос.АдресРесурса = "/bot" + Токен + "/sendPhoto";
// Указываем правильный Content-Type и передаем boundary
HTTPЗапрос.Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + ПараметрыЗапроса.Boundary);
// Передаем сформированные бинарные данные в тело запроса
HTTPЗапрос.УстановитьТелоИзДвоичныхДанных(ПараметрыЗапроса.Тело);
// Инициализируем защищенное соединение SSL
ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL();
// Создаем соединение с сервером Telegram
HTTPСоединение = Новый HTTPСоединение("api.telegram.org", , , , , , ЗащищенноеСоединение);
Попытка
ОтветHTTP = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
Если ОтветHTTP.КодСостояния = 200 Тогда
Сообщить("Картинка успешно отправлена без создания временных файлов!");
Иначе
Сообщить("Ошибка отправки! Код состояния сервера: " + ОтветHTTP.КодСостояния);
Сообщить("Ответ сервера: " + ОтветHTTP.ПолучитьТелоКакСтроку());
КонецЕсли;
Исключение
ВызватьИсключение "Не удалось установить соединение с сервером Telegram: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;
КонецПроцедуры
Давайте подробнее остановимся на понятии BOM (Byte Order Mark) — маркере последовательности байтов. При работе со старыми механизмами (например, объектом ЗаписьТекста) платформа автоматически добавляла в начало записываемой строки невидимые сервисные символы (три байта EF BB BF для UTF-8), которые сигнализировали текстовым редакторам о кодировке файла.
Для веб-сервисов, включая Telegram API, наличие этих лишних байтов в середине или начале multipart-запроса критично. Сервер может не распарсить разделитель boundary, если перед ним будут находиться байты BOM, и вернет ошибку некорректного запроса (например, Bad Request 400).
При использовании объекта ЗаписьДанных мы имеем полный контроль над выводом BOM. Четвертый параметр конструктора ЗаписьДанных отвечает за вывод этого маркера:
// Ложь в четвертом параметре отключает вывод BOM-маркера, что критически важно для HTTP-запросов
ЗаписьДанных = Новый ЗаписьДанных(Поток, КодировкаТекста.UTF8, , Ложь);
Использование этого параметра гарантирует, что в поток попадут только те байты, которые вы явно записали, без каких-либо скрытых префиксов.
Проанализируем преимущества работы с данными непосредственно в памяти по сравнению с использованием временных файлов на жестком диске:
Если спецификация стороннего API, с которым вы интегрируетесь, требует отправки данных исключительно в формате JSON (где тело запроса представляет собой сплошной текст), использование multipart/form-data становится невозможным. В таком случае мы должны применить кодирование Base64.
Давайте посмотрим, как быстро закодировать картинку в Base64 прямо в оперативной памяти и сформировать JSON-пакет:
Функция СформироватьJSONСКартинкой(КартинкаОбъект) Экспорт
// Получаем двоичные данные картинки
ДвоичныеДанные = КартинкаОбъект.ПолучитьДвоичныеДанные();
// Преобразуем двоичные данные в безопасную Base64-строку
СтрокаBase64 = Base64Строка(ДвоичныеДанные);
// Формируем структуру для отправки
СтруктураЗапроса = Новый Структура;
СтруктураЗапроса.Вставить("fileName", "image.jpg");
СтруктураЗапроса.Вставить("mimeType", "image/jpeg");
СтруктураЗапроса.Вставить("fileData", СтрокаBase64); // Передаем картинку текстом
// Сериализуем структуру в строку JSON
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, СтруктураЗапроса);
СтрокаJSON = ЗаписьJSON.Закрыть();
Возврат СтрокаJSON;
КонецФункции
Этот метод незаменим, если вы работаете с веб-сервисами, которые не поддерживают multipart-запросы и принимают файлы исключительно в текстовом виде внутри JSON или XML.
Несмотря на очевидные преимущества, разработчику следует помнить о разумных ограничениях работы с оперативной памятью. Если вам необходимо обработать и отправить файл экстремально большого размера (например, архив базы данных объемом несколько гигабайт), сборка такого файла полностью в оперативной памяти может привести к ошибке нехватки памяти (Out of Memory) на сервере 1С.
Для файлов размером до 50–100 МБ потоки в памяти подходят идеально. Для файлов большего объема рекомендуется использовать ФайловыйПоток, который позволяет обрабатывать данные порциями (буферизованно) без единовременной загрузки всего гигантского объема в RAM.
Мы детально изучили механизмы работы с потоками и двоичными данными в системе «1С:Предприятие 8.3». Отказ от устаревших методов работы через временные файлы на диске в пользу объектов ПотокВПамяти и ЗаписьДанных позволяет создавать быстрые, надежные и кроссплатформенные интеграционные решения, полностью соответствующие современным стандартам веб-разработки. Для проверки и отладки создаваемых запросов внутри конфигурации подойдёт инструмент для тестирования REST API в среде 1С.