В данной статье рассмотрим применение новых методов платформы 8.3.9 по работе с двоичными данными. Сначала немного теории.
Как было раньше
Ранее 1С предоставляла для работы с двоичными данными одноименный тип и некоторые методы работы с файлами. Думаю, многим известна ситуация, когда для отправки файла в формате multipart/form-data использовался метод «ОбъединитьФайлы». Это было связано с отсутствием методов по работе с внутренним содержимым двоичных данных.
Что нового
В версии 8.3.9 эта ситуация меняется. Разработчики платформы предоставили в наше распоряжение несколько новых типов и методов. Основным из них является обобщенный объект Поток. Потоков бывает три типа: Поток, ФайловыйПоток и ПотокВПамяти.
Основным преимуществом потоков является их способность работать с данными произвольного объема. Но в то же время они предоставляют ограниченные возможности.
Для получения расширенных возможностей необходимо на основе потока создать объект ЧтениеДанных. С помощью него можно читать отдельные байты, символы, числа.
Побайтовые операции
Для анализа работы новых методов возьмем за пример анализ параметров изображений для загрузки через API ВКонтакте.
Требования для загрузки фото в товар группы:
Ограничения: минимальный размер фото — 200x200px, сумма высоты и ширины не более 14000px, файл объемом не более 50 МБ.
Внимание! Формат файла JPG не обрабатывается в обработке.
Вес файла
Сначала на основе выбранного файла создадим ФайловыйПоток и сможем сразу получить Размер файла в байтах:
ПотокИсходный = ФайловыеПотоки.ОткрытьДляЧтения(Объект.ИмяФайла);
///1. Размер файла
Объект.РазмерФайла = Строка(ПотокИсходный.Размер() / 1024) + " Кб";
Формат файла
Каждый формат файла отличается друг от друга внутренней структурой. Отличительными особенностями каждого формата будем называть сигнатурами.
Зададим сигнатуры для каждого формата:
///Зададим сигнатуры нужных форматов
МассивДопустимыхФорматов = Новый Соответствие;
МассивДопустимыхФорматов.Вставить("PNG", РазложитьСтрокуВМассивПодстрок("137,80,78,71,13,10,26,10"));
//МассивДопустимыхФорматов.Вставить("JPEG", РазложитьСтрокуВМассивПодстрок("255,216"));
МассивДопустимыхФорматов.Вставить("GIF1", РазложитьСтрокуВМассивПодстрок("71,73,70,56,57,97"));
МассивДопустимыхФорматов.Вставить("BMP", РазложитьСтрокуВМассивПодстрок("66,77"));
МассивДопустимыхФорматов.Вставить("TIF", РазложитьСтрокуВМассивПодстрок("73,73,42,0"));
МассивДопустимыхФорматов.Вставить("TIF1", РазложитьСтрокуВМассивПодстрок("77,77,0,42"));
Каким образом получены сигнатуры?
В hex-записи сигнатура PNG файла выглядит так и состоит из 8 байт:
89 50 4E 47 0D 0A 1A 0A
Это можно проверить, открыв любой PNG файл в HEX-редакторе. Например, возьмем иконку месседжера Telegram размером 32*32.
Переведем эти значения из 16-ной системы в 10-ную и получим значения:
"137,80,78,71,13,10,26,10"
Аналогично поступаем с остальными форматами.
Проверяем формат файла
ЧтениеДанных = Новый ЧтениеДанных(ПотокИсходный, КодировкаТекста.ANSI, ПорядокБайтов.BigEndian);
ИскомыйТипФайла = ПроверитьСоответствиеТипаФайла(ЧтениеДанных, МассивДопустимыхФорматов);
Если ИскомыйТипФайла = Неопределено Тогда
Сообщить("Не поддерживаемый тип файла");
Возврат;
КонецЕсли;
&НаСервере
Функция ПроверитьСоответствиеТипаФайла(ЧтениеДанных, МассивДопустимыхФорматов)
МаксРазмер = 0;
Для Каждого ТипФайла Из МассивДопустимыхФорматов Цикл
МаксРазмер = Макс(МаксРазмер, ТипФайла.Значение.Количество());
КонецЦикла;
//прочитаем в новый буфер максимальный размер сигнатуры
БуферПроверка = ЧтениеДанных.ПрочитатьВБуферДвоичныхДанных(МаксРазмер);
Для Каждого ТипФайла Из МассивДопустимыхФорматов Цикл
//Хотелось бы конечно напрямую сравнить Буфер = Буфер, но так не работает
//Приходится сравнивать каждый байт
ДвоичнаяСигнатураФормата = ПолучитьСигнатуруФорматаВДвоичномВиде(ТипФайла.Значение);
БуферПроверкаТипаФайла = БуферПроверка.Прочитать(0, ДвоичнаяСигнатураФормата.Размер);
Сч = 0;
Равны = Истина;
Для Каждого Счет Из БуферПроверкаТипаФайла Цикл
Если Счет <> ДвоичнаяСигнатураФормата[Сч] Тогда
Равны = Ложь;
Прервать;
КонецЕсли;
Сч = Сч + 1;
КонецЦикла;
Если Равны Тогда
Возврат ТипФайла.Ключ;
КонецЕсли;
КонецЦикла;
Возврат НЕопределено;
КонецФункции
Т.к. буферы нельзя сравнить между собой, пришлось сравнивать байт за байтом. Если кто знает, как сделать красивее, пишите в комментариях.
Ищем размеры изображения
ЧтениеДанных.ПропуститьДо("IHDR", КодировкаТекста.ANSI);
Так же из спецификации формата PNG известно, что ширина и высота изображения занимают по 4 байта. Сначала прочитаем все 8 байтов, а потом отдельно ширину и высоту:
БуферЗаголовок = ЧтениеДанных.ПрочитатьВБуферДвоичныхДанных(8);
ОБъект.ШиринаИзображения = БуферЗаголовок.Прочитать(0, 4).ПрочитатьЦелое32(0, ПорядокБайтов.BigEndian);
Объект.ВысотаИзображения = БуферЗаголовок.Прочитать(4, 4).ПрочитатьЦелое32(0, ПорядокБайтов.BigEndian);
В методе ПрочитатьВБуферДвоичныхДанных() мы указываем общее количество байтов, которое хотим прочитать в буфер двоичных данных. Далее этот буфер читаем методом Прочитать(0, 4) – где 0 это позиция, а 4 – количество байтов для чтения.
Если мы продолжим читать этот экземпляр, то не сможем найти размеры изображения. Поэтому используем метод потока Перейти() и создаем новое ЧтениеДанных на основе нашего исходного потока. Дальше как обычно читаем размеры.
ТекПозиция = ПотокИсходный.ТекущаяПозиция();
ПотокИсходный.Перейти(-ТекПозиция, ПозицияВПотоке.Текущая);
ЧтениеДанныхGIF = Новый ЧтениеДанных(ПотокИсходный, КодировкаТекста.ANSI, ПорядокБайтов.LittleEndian);
БуферРазмеры = ЧтениеДанныхGIF.ПрочитатьВБуферДвоичныхДанных(10);
Объект.ШиринаИзображения = БуферРазмеры.Прочитать(6,2).ПрочитатьЦелое16(0, ПорядокБайтов.LittleEndian);
Объект.ВысотаИзображения = БуферРазмеры.Прочитать(8,2).ПрочитатьЦелое16(0, ПорядокБайтов.LittleEndian);
Таким образом, зная спецификации нужных форматов, мы проверяем формат файла, размеры изображения.
Отправка изображения через API ВКонтакте
Привожу пример работающего кода отправки сообщения multipart/form-data (проверено на отправке фото товара в группу ВКонтакте) (в обработке не приведен этот код):
//здесь получается объект с типом Картинка
Изображение = Номенклатура.ОсновноеИзображение.Хранилище.Получить();
//в функцию передаем двоичные данные картинки стандартным методом ПолучитьДвоичныеДанные()
ДвоичныеДанныеТело = СобратьИзображениеИзДвоичныхДанных(Изображение.ПолучитьДвоичныеДанные(), Boundary, ИмяФайлаДляЗагрузки);
Функция СобратьИзображениеИзДвоичныхДанных(ДвоичныеДанныеИзображения, Boundary, ИмяФайлаДляЗагрузки)
ПотокТело = Новый ПотокВПамяти();
ЗаписьДанных = Новый ЗаписьДанных(ПотокТело);
ЗаписьДанных.ЗаписатьСтроку("--" + Boundary);
ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""file""; filename=""" + ИмяФайлаДляЗагрузки + """");
ЗаписьДанных.ЗаписатьСтроку("Content-Type: image/jpeg");
ЗаписьДанных.ЗаписатьСтроку("");
ЗаписьДанных.Записать(ДвоичныеДанныеИзображения);
//Завершение раздела двоичных данных
ЗаписьДанных.ЗаписатьСтроку("--" + Boundary);
//Завершение сообщения для сервера
ЗаписьДанных.ЗаписатьСтроку("--" + Boundary + "--");
ЗаписьДанных.Закрыть();
ДвоичныеДанныеТело = ПотокТело.ЗакрытьИПолучитьДвоичныеДанные();
Возврат ДвоичныеДанныеТело;
КонецФункции