Файл в ДвоичныеДанные: чтение с диска, из базы и из сети
Содержание
Двоичные данные файла в 1С — это объект ДвоичныеДанные: байты файла целиком в оперативной памяти, без привязки к кодировке и формату. Чтобы получить их с диска, достаточно одной строки: Данные = Новый ДвоичныеДанные(ПутьКФайлу);. Остальные случаи — присоединённый файл, макет, вложение из письма, ответ веб-сервиса — отличаются только источником.
Зачем это нужно, видно по задачам: отправить файл по HTTP, сохранить скан в базу, переложить вложение между документами, посчитать контрольную сумму, перекодировать в Base64 для обмена. Ниже — способы от простого к сложному, причём для каждого указано, где он работает, а где нет.
Что внутри ДвоичныеДанные и чем объект отличается от файла
ДвоичныеДанные — неизменяемый объект. Его нельзя «дописать» или «обрезать»: можно только создать новый. Это важно для крупных файлов, потому что каждая копия занимает память.
Что с ним умеет делать платформа:
- 📏 узнать размер:
Данные.Размер(); - 💾 записать на диск:
Данные.Записать(ПутьКФайлу); - 🔤 перевести в строку Base64:
Base64Строка(Данные); - 📖 прочитать как строку в нужной кодировке:
ПолучитьСтрокуИзДвоичныхДанных(Данные, КодировкаТекста.UTF8); - 🖼 превратить в картинку:
Новый Картинка(Данные).
Путь к файлу нужен только на этапе создания. Дальше объект живёт сам по себе, и файл на диске можно удалять.
Если нужен не весь файл, а только часть (первые байты для определения формата, заголовок), не грузите его целиком. Для этого есть потоки, они описаны в отдельном разделе ниже.
Файл на диске: конструктор и потоки
Самый частый случай. Файл лежит на диске клиента или сервера, путь известен.
ПутьКФайлу = "C:\Обмен\scan.pdf";
Файл = Новый Файл(ПутьКФайлу);
Если Не Файл.Существует() Тогда
ВызватьИсключение "Файл не найден: " + ПутьКФайлу;
КонецЕсли;
Данные = Новый ДвоичныеДанные(ПутьКФайлу);
Сообщить("Прочитано байт: " + Данные.Размер());
Проверка Существует() здесь не формальность. Без неё вы получите общее исключение платформы, из которого неочевидно, чей это путь и что с ним не так.
Где выполняется код, там и ищется путь
Это главный источник путаницы. Путь C:\Обмен\scan.pdf в серверной процедуре означает диск сервера. Пользователь видит свой диск, а сервер — свой.
| Где выполняется код | Чей диск | Что учесть |
|---|---|---|
| Клиент (файловая или клиент-серверная база, тонкий клиент) | компьютер пользователя | нужны права на чтение каталога |
| Сервер 1С | сервер | пользователь службы USR1CV8 должен иметь доступ к каталогу |
| Веб-клиент | диск клиента напрямую не виден | только через диалог выбора и помещение файла на сервер |
Если сервер 1С работает под Linux, пути пишутся с прямыми слэшами и регистр имён имеет значение.
Файл из диалога выбора
Для интерактивной работы файл нужно сначала выбрать, а потом передать на сервер. Самый переносимый способ — помещение файла во временное хранилище:
&НаКлиенте
Процедура ВыбратьФайл(Команда)
Оповещение = Новый ОписаниеОповещения("ПослеПомещенияФайла", ЭтотОбъект);
НачатьПомещениеФайла(Оповещение, , , Истина, УникальныйИдентификатор);
КонецПроцедуры
&НаКлиенте
Процедура ПослеПомещенияФайла(Результат, Адрес, ВыбранноеИмяФайла, ДополнительныеПараметры) Экспорт
Если Результат Тогда
ОбработатьНаСервере(Адрес);
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ОбработатьНаСервере(Адрес)
Данные = ПолучитьИзВременногоХранилища(Адрес);
// Данные имеют тип ДвоичныеДанные
КонецПроцедуры
Этот вариант работает и в тонком, и в веб-клиенте. Файл уходит на сервер один раз, дальше вы работаете с ним в серверном контексте.
Присоединённые файлы, макеты и значения в базе
Файл уже может лежать внутри информационной базы. Способ зависит от того, как он там хранится.
Присоединённый файл
В конфигурациях на БСП присоединённые файлы живут в справочниках вида «…ПрисоединенныеФайлы». Двоичные данные получают через модуль РаботаСФайлами: он сам разбирается, где физически лежит файл — в базе или в томах на диске.
Данные = РаботаСФайлами.ДвоичныеДанныеФайла(ПрисоединенныйФайл);
Здесь ПрисоединенныйФайл — ссылка на элемент справочника файлов. Обращаться напрямую к реквизитам хранилища не стоит: структура зависит от версии БСП и от того, включено ли хранение в томах.
Хранилище значения
Если в реквизите типа ХранилищеЗначения лежит файл, достаточно метода Получить():
Данные = Объект.Вложение.Получить();
Если ТипЗнч(Данные) <> Тип("ДвоичныеДанные") Тогда
// в хранилище лежит что-то другое
КонецЕсли;
Проверка типа нужна, потому что в такое хранилище кладут что угодно: картинки, таблицы значений, произвольные структуры. Вернуться может Неопределено, если реквизит пуст.
Двоичный макет и картинка
- Макет типа «Двоичные данные» из конфигурации:
Данные = ПолучитьОбщийМакет("ИмяМакета");илиСправочники.Имя.ПолучитьМакет("ИмяМакета"). - Картинка из библиотеки или реквизита:
Картинка.ПолучитьДвоичныеДанные(). - Табличный документ: сначала
ТабДок.Записать(ВремФайл, ТипФайлаТабличногоДокумента.PDF), потом чтение файла конструктором. Прямого перевода в двоичные данные без записи на диск нет.
Временные файлы создавайте через ПолучитьИмяВременногоФайла() и удаляйте в Попытка … Исключение, иначе каталог временных файлов на сервере будет разрастаться.
Новый ДвоичныеДанные(Путь). Код должен выполняться там, где виден этот диск.НачатьПомещениеФайла, затем ПолучитьИзВременногоХранилища(Адрес) на сервере.РаботаСФайлами.ДвоичныеДанныеФайла(Файл) в конфигурации на БСП.Реквизит.Получить() и проверка типа результата.HTTPОтвет.ПолучитьТелоКакДвоичныеДанные().Получение двоичных данных из HTTP-ответа и запроса
Для обмена с внешними сервисами двоичные данные чаще всего приходят в теле ответа.
Соединение = Новый HTTPСоединение("example.org", 443, , , , 30, Новый ЗащищенноеСоединениеOpenSSL);
Запрос = Новый HTTPЗапрос("/files/report.pdf");
Ответ = Соединение.Получить(Запрос);
Если Ответ.КодСостояния = 200 Тогда
Данные = Ответ.ПолучитьТелоКакДвоичныеДанные();
Данные.Записать(КаталогВременныхФайлов() + "report.pdf");
Иначе
ВызватьИсключение "Сервер вернул код " + Ответ.КодСостояния;
КонецЕсли;
Обратное направление — отправка файла. Тело запроса заполняют так:
Запрос = Новый HTTPЗапрос("/upload");
Запрос.Заголовки.Вставить("Content-Type", "application/pdf");
Запрос.УстановитьТелоИзДвоичныхДанных(Данные);
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
Если файл большой, используйте ПолучитьТелоКакПоток() для ответа: так вы пишете данные на диск порциями и не держите всё в памяти.
Для веб-сервисов, которые возвращают файл в виде Base64, схема другая: берёте строку из JSON или XML и переводите её функцией Base64Значение(Строка). Результат — те же ДвоичныеДанные.
| Источник | Метод | Особенность |
|---|---|---|
| Тело HTTP-ответа | ПолучитьТелоКакДвоичныеДанные() | весь ответ в памяти |
| Тело HTTP-ответа, большой файл | ПолучитьТелоКакПоток() | читаете и пишете порциями |
| Строка Base64 | Base64Значение(Строка) | размер в памяти примерно на треть больше исходных байт |
| Строка в известной кодировке | ПолучитьДвоичныеДанныеИзСтроки(Строка, Кодировка) | кодировку нужно указывать явно |
Чтение большого файла по частям
Когда файл весит сотни мегабайт, конструктор Новый ДвоичныеДанные — плохой выбор: платформа выделит память под весь файл, а на клиенте это быстро кончается. Решение — читать буфером через ФайловыйПоток.
Поток = Новый ФайловыйПоток(ПутьКФайлу, РежимОткрытияФайла.Открыть, ДоступКФайлу.Чтение);
РазмерБуфера = 1048576; // 1 МБ
Буфер = Новый БуферДвоичныхДанных(РазмерБуфера);
Всего = 0;
Пока Истина Цикл
Прочитано = Поток.Прочитать(Буфер, 0, РазмерБуфера);
Если Прочитано = 0 Тогда
Прервать;
КонецЕсли;
Всего = Всего + Прочитано;
// здесь обработка первых Прочитано байт буфера
КонецЦикла;
Поток.Закрыть();
Сообщить("Обработано байт: " + Всего);
Что делать с очередным куском, решаете вы: посчитать хеш, отправить частями, записать в другой поток, найти сигнатуру. Для расчёта контрольной суммы есть готовый объект ХешированиеДанных, в него можно передавать данные кусками.
Главное правило: поток обязательно закрывайте, иначе файл останется заблокированным до завершения сеанса. Надёжнее обернуть чтение в Попытка … Исключение и закрывать поток в обеих ветках.
Размер буфера подбирайте под задачу. Слишком маленький (килобайт) превратит чтение в тысячи вызовов, слишком большой съест память зря. Для типичных файлов хватает 1–4 МБ.
Если нужны только первые байты файла, например чтобы отличить PDF от ZIP, прочитайте 8–16 байт потоком. Не загружайте файл целиком ради заголовка.
Альтернатива: COM и внешние компоненты
Родной способ закрывает почти всё. COM нужен в редких случаях: старый код, который уже на нём написан, или операция, которой нет в платформе (например, получить владельца файла).
Поток = Новый COMОбъект("ADODB.Stream");
Поток.Type = 1; // двоичный режим
Поток.Open();
Поток.LoadFromFile(ПутьКФайлу);
Размер = Поток.Size;
Поток.Close();
Здесь важны ограничения:
- ⚠️ COM работает только в Windows и только там, где код выполняется как клиентский или серверный на Windows;
- ⚠️ в веб-клиенте и на сервере под Linux COM недоступен совсем;
- ⚠️ вернуть массив байт из
ADODB.StreamвДвоичныеДанныенапрямую нельзя, обычно файл просто сохраняют на диск и читают конструктором. Тогда COM теряет смысл.
Внешние компоненты (Native API, подключение через ПодключитьВнешнююКомпоненту) оправданы, когда файл надо читать из нестандартного источника: сканер, терминал, USB-устройство. Для обычных файлов выбирайте штатные средства.
В безопасном режиме и в расширениях COM и внешние компоненты, как правило, запрещены. Это ещё один довод за встроенный язык.
Что делать с полученными данными: три типовых примера
Получить объект — половина дела. Вот что с ним делают чаще всего.
- Сохранить как файл для пользователя:
Данные.Записать(Путь), а в веб-клиенте — поместить во временное хранилище черезПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор)и отдать адрес форме. - Положить в реквизит базы:
Объект.Вложение = Новый ХранилищеЗначения(Данные, Новый СжатиеДанных(9));. Сжатие полезно для текстовых форматов и почти бесполезно для уже сжатых (JPEG, ZIP, DOCX). - Передать во внешний сервис: тело HTTP-запроса или строка Base64 в JSON.
Пример контрольной суммы, чтобы убедиться, что файл не повредился при передаче:
Хеш = Новый ХешированиеДанных(ХешФункция.SHA256);
Хеш.Добавить(Данные);
Контрольная = Хеш.ХешСумма;
Сравните значение до и после передачи: если они совпали, данные дошли без потерь.
Типичные ошибки и как их исправить
ТипЗнч до обращения к методам.Как не гонять лишние мегабайты
Двоичные данные «тяжёлые» не только на диске. При передаче между клиентом и сервером они копируются, и каждая копия стоит времени.
- Работайте с файлом там, где он лежит. Если он на сервере, не тащите его на клиент, чтобы там посчитать размер.
- Передавайте адрес временного хранилища, а не сами данные. Адрес — это короткая строка, а данные остаются на сервере.
- Не превращайте данные в Base64 без нужды: строка получается заметно больше исходных байт и дольше обрабатывается.
- Очищайте временное хранилище
УдалитьИзВременногоХранилища(Адрес), когда данные больше не нужны. - Для множества мелких файлов открывайте поток один раз, а не вызывайте конструктор в цикле с нулевым переиспользованием.
Если задача разовая, например выгрузить десяток документов, оптимизировать нечего. Заботиться о памяти стоит, когда файлов тысячи или один из них больше нескольких десятков мегабайт.
Частые вопросы
Чем ДвоичныеДанные отличаются от Файла и от ХранилищаЗначения?
Файл — это описание файла на диске (имя, путь, размер), содержимого в нём нет. ДвоичныеДанные — само содержимое. ХранилищеЗначения — контейнер для хранения любого значения в базе, в том числе двоичных данных.Можно ли получить двоичные данные без записи на диск?
Как вернуть двоичные данные обратно в файл?
Данные.Записать(ПутьКФайлу). Каталог должен существовать, а у процесса должны быть права на запись.Как узнать размер файла, не читая его?
Файл: Новый Файл(Путь).Размер(). Содержимое при этом не загружается.