Загрузка файла из произвольной WEB-формы через HTTP-сервис 1С на сервер.

Использование буфера двоичных данных.

Казалось бы, нет ничего проще, создать HTTP-сервис с методом POST, создать простейшую web-страничку с формой для загрузки файла. Например, вот такую:

<form enctype="multipart/form-data" method="post" action="http://serv1c/base/hs/test/load">
<p>
<input type="file" name="f">
<input type="submit" value="Отправить">
</p>
</form>

Открыть страничку в браузере, выбрать требуемый файл, нажать на форме кнопку «Отправить». А в HTTP-сервисе выполнить функцию ПолучитьТелоКакДвоичныеДанные и сохранить эти двоичные данные в файл в любое доступное место. Но! Не тут-то было.

Получаем двоичные данные не только файла, но и дополнительную метаинформацию.  При этом сохраненный файл представляет из себя что-то подобное этому:

——————————7e22f312a0802
Content-Disposition: form-data; name="f"; filename="myfile.png"
Content-Type: image/png
‰PNGIHDRxtyx›sRGB®ОйgAMA±Џ ьaPLTEяяяgvмc pHYsWU) ~NrIDAThCнЌKv$7=чїґP&:‰"UТі—‹О-эПwьЉ•e{UН©KЮцїьяя1f’©?еvп }=Hэ)·{чилAкO№Э»G_RКнЮ=ъ1Вy*аAссЬНнС·ОSЉЏ·аnnЏѕЌpћ xP|јws{фm„уTАѓвг-ё›ЫЈo#њS“Ь`z°ЇR<ъ6В95Й ¦ы*ЕЈo#њS“Ь`z°ЇR<ъ6В95Й ¦ы*ЕЈo#њїлН-џЁг…вз>KpFНМ@йOФсBсsџ%8ЈЙm#ѓв†¦FхWЯF8ЈЙm#ѓв†¦FхWЯF8ЈЙm#ѓв†¦FхWЯF8ЈЙm#ѓв†¦FхWЯF
——————————7e22f312a0802—

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

Не буду детально углубляться в описание структуры полученных данных, т.к. задача стоит — передать файл на сервер и сохранить его. Поэтому все коротко и по порядку.

Итак, для начала нужно из заголовка основного web-запроса получить уникальную строку-разделитель (в файле-примере это "—————————7e22f312a0802"). Она нужна, в первую очередь для разделения получаемой информации, указанной на форме. В данной статье рассматривается форма для загрузки только одного файла, но ведь на форме могут быть еще и другие поля ввода, которые и разделяются уникальной строкой-разделителем.

Строку-разделитель следует получить из атрибута «boundary» заголовка «Content-Type» основного запроса, например так:

 

ВзялГраницу="";
Для каждого ВзялЗаголовок Из Запрос.Заголовки Цикл
Если ВзялЗаголовок.Ключ="Content-Type" Тогда
ВзялЗначение=ВзялЗаголовок.Значение;
Поз=СтрНайти(ВзялЗначение, "boundary=");
Если Поз>0 Тогда
ВзялГраницу = Сред(ВзялЗначение, Поз+СтрДлина("boundary="));
КонецЕсли;
КонецЕсли;
КонецЦикла;

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

Если ПустаяСтрока(ВзялГраницу)=Ложь Тогда

ВзялПоток = Запрос.ПолучитьТелоКакПоток();
ВзялРазмер = ВзялПоток.Размер();
ВзялБуфер = Новый БуферДвоичныхДанных(ВзялРазмер);
ВзялПоток.Прочитать(ВзялБуфер, 0, ВзялРазмер);
ВзялПоток = Неопределено; //освобождаем память

!!! Хотелось бы предупредить, что буфер двоичных данных – это выделенная память на сервере. И чем больше размер буфера, тем больше этой самой памяти расходуется. Следить за размером загружаемых файлов и устанавливать ограничения следует на web-сервере, на котором опубликован http-сервис. И по возможности, в тексте модуля, освобождать память, исключая неиспользуемые буферы!!!

В дальнейшей работе нам понадобится вспомогательный буфер-разделитель. В нем будет храниться последовательность 0x0D, 0x0A или 13,10 или проще говоря «возврат каретки» и «перевод строки». Указанная последовательность является разделителем элементов полученной информации:

БуферРазделитель = Новый БуферДвоичныхДанных(2);
БуферРазделитель.Установить(0,13);
БуферРазделитель.Установить(1,10);

Далее возьмем только «нужную» информацию, которая расположена до последней строки-разделителя:

ВзялБуфер=ВзялБуфер.Разделить(БуферРазделитель.Соединить(ПолучитьБуферДвоичныхДанныхИзСтроки("--"+ВзялГраницу+"--")))[0];

!!! В начале всех строк-разделителей добавлена последовательность 0x0D, 0x0A и строка «—», строка «—» добавлена так же и в конце последней строки-разделителя !!!

Теперь получим массив всех элементов формы, которые, как уже говорилось ранее, разделены строкой-разделителем: 

ВзялМассивЭлементовФормы=ВзялБуфер.Разделить(БуферРазделитель.Соединить(ПолучитьБуферДвоичныхДанныхИзСтроки("--"+ВзялГраницу)));
ВзялБуфер = Неопределено; //чистим память

Ну и не остается ничего другого, как начать перебирать полученные элементы формы и искать в них любое упоминание о передаваемом файле:

Для каждого ВзялЭлемент Из ВзялМассивЭлементовФормы Цикл

Метаинформация о полученном элементе и его значение отделены так называемой «пустой строкой» или последовательностью 0x0D, 0x0A, 0x0D, 0x0A (13,10,13,10).  Т.е. это «двойной» буфер-разделитель, созданный ранее. И у нас наконец-то появилась возможность отделения двоичной информации от текстовой:

ВзялМассивБуферов = ВзялЭлемент.Разделить(БуферРазделитель.Соединить(БуферРазделитель));
ВзялЭлемент = Неопределено; //свобода памяти!

В результате у нас есть массив, в котором первый элемент (с индексом 0) содержит метаинформацию, а все последующие – предполагаемые данные передаваемого файла. Да, да, все последующие, т.к. «пустая строка» (0x0D, 0x0A, 0x0D, 0x0A или 13,10,13,10), о которой говорилось раньше, может присутствовать в данных самого файла и при разделении она учитывается процессом разделения, разбивающим данные файла на части.

Далее необходимо определить, является ли выбранный элемент формы файлом для загрузки. Для этого из полученной метаинформации извлекаем все заголовки и у каждого ищем атрибут «filename». Заголовки отделены друг от друга уже известной последовательностью 0x0D, 0x0A (13,10), которая хранится в буфере-разделителе:

ВзялЗаголовки=ВзялМассивБуферов[0].Разделить(БуферРазделитель);
ВзялМассивБуферов[0]=Неопределено; //очисти сознание
ВзялИмяФайла="";
Для каждого ВзялЗаголовок Из ВзялЗаголовки Цикл

У нас есть возможность преобразовать двоичные данные в строку, сделаем это:

ВзялСтрокуЗаголовка=ПолучитьСтрокуИзБуфераДвоичныхДанных(ВзялЗаголовок);
Поз=СтрНайти(ВзялСтрокуЗаголовка, "filename=");
Если Поз>0 Тогда

Есть атрибут «filename», значит этот элемент формы хранит в себе загружаемый файл. Теперь нужно получить его имя, выделив его из полного пути к нему и убрав кавычки:
 

  ВзялПолноеИмяФайла=СтрЗаменить(Сред(ВзялСтрокуЗаголовка, Поз+СтрДлина("filename=")),"""","");
ВзялДлину=СтрДлина(ВзялПолноеИмяФайла);
Для А=0 По ВзялДлину-1 Цикл
ВзялСимвол=Сред(ВзялПолноеИмяФайла, ВзялДлину-А, 1);
Если ВзялСимвол="" ИЛИ ВзялСимвол="/" Тогда
Прервать;
КонецЕсли;
ВзялИмяФайла = ВзялСимвол + ВзялИмяФайла;
КонецЦикла;
Прервать;
КонецЕсли;
КонецЦикла;

Если получено имя загружаемого файла, значит мы можем его теперь «собрать» и записать в наше надежное место с этим именем:

Если ПустаяСтрока(ВзялИмяФайла)=Ложь Тогда

Поскольку первый элемент массива хранит буфер с метаинформацией, то последующие – буферы двоичных данных файла. Что ж объединим их в один:

  ВзялБуферДанныхФайла=ВзялМассивБуферов[1];
Для А = 2 По ВзялМассивБуферов.ВГраница() Цикл

Не забываем, что массив был создан из частей, разделенных «пустой строкой» (0x0D, 0x0A, 0x0D, 0x0A или 13,10,13,10) и следовательно, этот разделитель должен быть восстановлен при склейке элементов массива:

      ВзялБуферДанныхФайла = ВзялБуферДанныхФайла.Соединить(БуферРазделитель.Соединить(БуферРазделитель.Соединить(ВзялМассивБуферов[А])));
ВзялМассивБуферов[А]=Неопределено; //чистые мозги
КонецЦикла;

Ну вот и все. Данные собраны и теперь их можно записать в файл:
 

    Попытка
ВзялДанныеФайла = ПолучитьДвоичныеДанныеИзБуфераДвоичныхДанных(ВзялБуферДанныхФайла);
//здесь следует указать путь для сохранения файла для примера указан диск d: сервера:
ВзялДанныеФайла.Записать("d:"+ВзялИмяФайла);
ВзялДанныеФайла = Неопределено; // просветление сознания
Исключение

Или не записать. Подобную ситуацию нужно будет изучить детально в каждом конкретном случае:
 

      Ответ.КодСостояния = 400;
Ответ.Причина = "Ошибка получения двоичных данных файла из запроса.";
КонецПопытки;
КонецЕсли;
КонецЦикла;
Иначе

Web-форма должна быть обязательно с указанным enctype="multipart/form-data" иначе неизбежно будет ситуация ниже.

  Ответ.КодСостояния = 400;
Ответ.Причина = "Ошибка получения границы-разделителя для multipart/form-data.";
КонецЕсли; 

   
Платформа 1С:Предприятие 8.3 (8.3.11.3034).

 

3 Comments

  1. EmpireSer

    Вообще заголовки должны читаться не зависимо от регистра (а в 1С в программном коде поиск регистрозависимый). Это написано в стандартах описывающих протокол HTTP.

    Тоже самое и о параметрах в заголовке «Content-Type»: RFC 2045. Так же там написано, что параметр может быть в кавычках, а может и не иметь их. Для них там только прописан разделить ; (точка с запятой). Так что «filename» в одном из браузеров (забыл в каком) будет без кавычек. Но у тебя делается «СтрЗаменить», хотя для Линукса двойная кавычка — это легальный символ, поэтому кавычки должны проверяться относительно начала и конца строки, а не «СтрЗаменить».

    Reply
  2. bobank

    (1) Да, я в курсе этого. Хотя никогда и не встречал на практике «гуляние» регистра символов в заголовках и параметрах. Ведь здесь я не описывал законченное решение. Лишь поделился как можно использовать буфер двоичных данных для выполнения более-менее практической задачи.

    Reply
  3. kembrik

    Спасибо, конец мучениям!

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *