Для HTTP запроса типа POST существует два варианта передачи полей из HTML форм, а именно, используя алгоритм и . Алгоритм первого типа создавался давным-давно, когда в языке HTML еще не предусматривали возможность передачи файлов через HTML формы.
Со временем возникла необходимость через формы отсылать еще и файлы. Тогда консорциум W3C взялся за доработку формата POST запроса. К тому времени уже достаточно широко применялся формат (Multipurpose Internet Mail Extensions — многоцелевые расширения протокола для формирования Mail сообщений), поэтому, чтобы не изобретать велосипед заново, решили использовать часть данного формата формирования сообщений для создания POST запросов в протоколе HTTP.
Главное отличие от в том, что тело запроса теперь можно поделить на разделы, которые разделяются границами. Каждый раздел может иметь свой собственный заголовок для описания данных, которые в нем хранятся, т.е. в одном запросе можно передавать данные различных типов (как в теле письма можно одновременно с текстом передавать файлы). Пример запроса:
Content-Type: multipart/form-data; boundary=
Content-Length: 417339
Content-Disposition: form-data; name=»name»
Евгений
Content-Disposition: form-data; name=»message»
Какое-то сообщение от пользователя
Content-Disposition: form-data; name=»upload»; filename=»image.jpg»
Content-Type: image/jpeg
Content-Transfer-Encoding: binary
Boundary (граница) — это последовательность байтов, которая не должна встречаться внутри передаваемых данных. Content-Length — суммарный объём, включая дочерние заголовки. Само содержимое полей при этом оставляется «как есть».
Потребовалось отправить картинку в теле запроса в HTTP сервис методом POST, куча реализаций, но есть маленькие нюансы, если хотя бы кому-то поможет, то уже это было не напрасно.
Все как всегда, легко и просто
Создаем соединение через Новый HTTPСоединение()
Создаем HTTPЗапрос через Новый HTTPЗапрос()
Дальше все кажется логичным и простым
Методом HTTP запроса УстановитьТелоИзДвоичныхДанных() передаем в него картинку и отправляем
Было бы очень легко и интуитивно понятно, но тут начинается веселье, как по мне
Формат multipart/form-data требует очень четкой структуры данных
Через Postman смотрим snippet
// ЗАГОЛОВКИ
Content-Length: 232 // длина сообщения, не обязательна
Content-Type: multipart/form-data; boundary=—-WebKitFormBoundary7MA4YWxkTrZu0gW // определение типа данных и указание разделителя
// Тело запроса
—-WebKitFormBoundary7MA4YWxkTrZu0gW // разделитель
Content-Disposition: form-data; name=»file»; filename=»Scan.jpg» // описание поля по формату с указанием имени поля «file» и название данных для принимающей стороны
Content-Type: image/jpeg // тип данных
(data) // сами данные
—-WebKitFormBoundary7MA4YWxkTrZu0gW // закрывающий разделитель
Тоже все понятно и легко вроде как, но я словил так называемую ошибку, над которой даже не задумался
1. Структура должна быть обязательно именно такой, повторяя каждый отступ и перенос
Разделитель, Символ. ВК, Символ. ПС
Описание, Символ. ВК, Символ. ПС
ОписаниеТипа, Символ. ВК, Символ. ПС,
Символ. ВК, Символ. ПС
ДвочныеДанные, Символ. ВК, Вимвол. ПС
Разделитель
2. При записи через поток при определении ЗаписьДанных можно переопределить
РазделительСтрок — Значение по умолчанию: ПС
КонвертируемыйРазделительСтрок — Значение по умолчанию: ВК + ПС
Получается, в мои данные добавлялись лишние символы, помогло сохранение данных из записи данных в файл и просмотр символов.
В итоге код, который отработал, в конечном варианте выглядит вот так:
КартинкаИмяПараметра = «file»;
КартинкаИмяФайла = «Scan.jpg»;
Картинка = Новый Картинка(АдресФайла); // любым удобным способом, по факту нужны ДД изображения
//
Разделитель = СтрЗаменить(Новый УникальныйИдентификатор(), «-«, «»); // все что угодно, можно даже руками указать «razzdell», главное что бы оно было уникальное во всем тексте тела
// —Отладочный кусок
// ИмяВременногоФайла = ПолучитьИмяВременногоФайла(«txt»); // временный файл для сохранения полученного тела для запроса
// ++Отладочный кусок
Тело = Новый ПотокВПамяти();
ЗаписьДанных = Новый ЗаписьДанных(Тело, , , Символы. ВК + Символы. ПС, «»); // то самое переопределение разделителей
ЗаписьДанных. ЗаписатьСтроку(«—» + Разделитель);
ЗаписьДанных. ЗаписатьСтроку(«Content-Disposition: form-data; name=» + КартинкаИмяПараметра + «; filename=» + КартинкаИмяФайла);
ЗаписьДанных. ЗаписатьСтроку(«Content-Type: image/jpeg»);
ЗаписьДанных. ЗаписатьСтроку(«»); // пустая строка для доп переноса и воспроизведения структуры тела
ЗаписьДанных. Записать(Картинка. ПолучитьДвоичныеДанные()); // добавляет данные но каретка остается после данных
ЗаписьДанных. ЗаписатьСтроку(«»); // записать не добавляет переносов, добавляем их пустой строкой
ЗаписьДанных. ЗаписатьСтроку(«—» + Разделитель + «—«); // добавляются еще доп переносы, но они уже не на что не влияют, так же после разделителя добавляем «—«, должно обозначать конец тела
ЗаписьДанных. Закрыть();
ДанныеТела = Тело. ЗакрытьИПолучитьДвоичныеДанные(); // получаем дело в дд, можно и через запись в файл, просто лишний раз не трогаем диски и не делаем доп операций
// —Отладочный кусок
// ДанныеТела. Записать(ИмяВременногоФайла); // записывает ДД в txt файл
// ++Отладочный кусок
HTTPЗапрос. УстановитьТелоИзДвоичныхДанных(ДанныеТела); // установили тело
HTTPЗапрос. Заголовки. Вставить(«Content-Length», XMLСтрока(ДанныеТела. Размер()));
HTTPЗапрос. Заголовки. Вставить(«Content-Type», «multipart/form-data; boundary=» + разделитель);
ОтветHTTP = Соединение. ОтправитьДляОбработки(HTTPЗапрос); // финал
Если есть возможность улучшить, адаптировать, сделать проще, я только за, жду в комментариях.
Использование буфера двоичных данных.
Казалось бы, нет ничего проще, создать HTTP-сервис с методом POST, создать простейшую web-страничку с формой для загрузки файла. Например, вот такую:
Открыть страничку в браузере, выбрать требуемый файл, нажать на форме кнопку «Отправить». А в HTTP-сервисе выполнить функцию ПолучитьТелоКакДвоичныеДанные и сохранить эти двоичные данные в файл в любое доступное место. Но! Не тут-то было.
Получаем двоичные данные не только файла, но и дополнительную метаинформацию. При этом сохраненный файл представляет из себя что-то подобное этому:
Как говорится, «все смешалось в доме Облонских» и текстовая информация и двоичная, которую получить из, условно говоря, гибридного содержимого, средствами 1С — затея так себе. Но благодаря появлению в платформе возможностей работы с буфером двоичных данных можно закатав рукава решить эту задачу.
Не буду детально углубляться в описание структуры полученных данных, т.к. задача стоит — передать файл на сервер и сохранить его. Поэтому все коротко и по порядку.
Итак, для начала нужно из заголовка основного web-запроса получить уникальную строку-разделитель (в файле-примере это «—————————7e22f312a0802»). Она нужна, в первую очередь для разделения получаемой информации, указанной на форме. В данной статье рассматривается форма для загрузки только одного файла, но ведь на форме могут быть еще и другие поля ввода, которые и разделяются уникальной строкой-разделителем.
Строку-разделитель следует получить из атрибута «boundary» заголовка «Content-Type» основного запроса, например так:
Далее нам нужно, для успешного отделения двоичных данных от текстовых прочитать полученную информацию из запроса в буфер двоичных данных:
Если ПустаяСтрока(ВзялГраницу)=Ложь Тогда
ВзялПоток = Запрос. ПолучитьТелоКакПоток();
ВзялРазмер = ВзялПоток. Размер();
ВзялБуфер = Новый БуферДвоичныхДанных(ВзялРазмер);
ВзялПоток. Прочитать(ВзялБуфер, 0, ВзялРазмер);
ВзялПоток = Неопределено; //освобождаем память
!!! Хотелось бы предупредить, что буфер двоичных данных – это выделенная память на сервере. И чем больше размер буфера, тем больше этой самой памяти расходуется. Следить за размером загружаемых файлов и устанавливать ограничения следует на web-сервере, на котором опубликован http-сервис. И по возможности, в тексте модуля, освобождать память, исключая неиспользуемые буферы!!!
В дальнейшей работе нам понадобится вспомогательный буфер-разделитель. В нем будет храниться последовательность 0x0D, 0x0A или 13,10 или проще говоря «возврат каретки» и «перевод строки». Указанная последовательность является разделителем элементов полученной информации:
БуферРазделитель = Новый БуферДвоичныхДанных
;
БуферРазделитель. Установить(0,13);
БуферРазделитель. Установить(1,10);
Далее возьмем только «нужную» информацию, которая расположена до последней строки-разделителя:
!!! В начале всех строк-разделителей добавлена последовательность 0x0D, 0x0A и строка «—», строка «—» добавлена так же и в конце последней строки-разделителя !!!
Теперь получим массив всех элементов формы, которые, как уже говорилось ранее, разделены строкой-разделителем:
ВзялМассивЭлементовФормы=ВзялБуфер. Разделить(БуферРазделитель. Соединить(ПолучитьБуферДвоичныхДанныхИзСтроки(«—«+ВзялГраницу)));
ВзялБуфер = Неопределено; //чистим память
Ну и не остается ничего другого, как начать перебирать полученные элементы формы и искать в них любое упоминание о передаваемом файле:
Для каждого ВзялЭлемент Из ВзялМассивЭлементовФормы Цикл
Метаинформация о полученном элементе и его значение отделены так называемой «пустой строкой» или последовательностью 0x0D, 0x0A, 0x0D, 0x0A (13,10,13,10). Т.е. это «двойной» буфер-разделитель, созданный ранее. И у нас наконец-то появилась возможность отделения двоичной информации от текстовой:
ВзялМассивБуферов = ВзялЭлемент. Разделить(БуферРазделитель. Соединить(БуферРазделитель));
ВзялЭлемент = Неопределено; //свобода памяти!
В результате у нас есть массив, в котором первый элемент (с индексом 0) содержит метаинформацию, а все последующие – предполагаемые данные передаваемого файла. Да, да, все последующие, т.к. «пустая строка» (0x0D, 0x0A, 0x0D, 0x0A или 13,10,13,10), о которой говорилось раньше, может присутствовать в данных самого файла и при разделении она учитывается процессом разделения, разбивающим данные файла на части.
Далее необходимо определить, является ли выбранный элемент формы файлом для загрузки. Для этого из полученной метаинформации извлекаем все заголовки и у каждого ищем атрибут «filename». Заголовки отделены друг от друга уже известной последовательностью 0x0D, 0x0A (13,10), которая хранится в буфере-разделителе:
У нас есть возможность преобразовать двоичные данные в строку, сделаем это:
Есть атрибут «filename», значит этот элемент формы хранит в себе загружаемый файл. Теперь нужно получить его имя, выделив его из полного пути к нему и убрав кавычки:
ВзялПолноеИмяФайла=СтрЗаменить(Сред(ВзялСтрокуЗаголовка, Поз+СтрДлина(«filename=»)),»»»»,»»);
ВзялДлину=СтрДлина(ВзялПолноеИмяФайла);
Для А=0 По ВзялДлину-1 Цикл
ВзялСимвол=Сред(ВзялПолноеИмяФайла, ВзялДлину-А, 1);
Если ВзялСимвол=»» ИЛИ ВзялСимвол=»/» Тогда
Прервать;
КонецЕсли;
ВзялИмяФайла = ВзялСимвол + ВзялИмяФайла;
КонецЦикла;
Прервать;
КонецЕсли;
КонецЦикла;
Если получено имя загружаемого файла, значит мы можем его теперь «собрать» и записать в наше надежное место с этим именем:
Если ПустаяСтрока(ВзялИмяФайла)=Ложь Тогда
Поскольку первый элемент массива хранит буфер с метаинформацией, то последующие – буферы двоичных данных файла. Что ж объединим их в один:
Не забываем, что массив был создан из частей, разделенных «пустой строкой» (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).
В данной статье собран мой опыт по передаче данных на веб-сервер.
В ходе реализации одного проекта потребовалось организовать автоматическую передачу данных на веб-сервер посредством регламентного задания.
Покопавшись прежде всего в мануалах, синтаксис-помощнике 1С Предприятия, порыскав в Сети, я с удивлением обнаружил наличие отсутствия каких-либо вменяемых материалов по теме. Вернее, материалы в Сети есть, но там больше вопросы с абстрактными ответами.
В статье собран мой опыт по передаче данных на веб-сервер.
В ходе реализации одного проекта потребовалось организовать автоматическую передачу данных на веб-сервер посредством регламентного задания. Покопавшись прежде всего в мануалах, синтаксис-помощнике 1С Предприятия, порыскав в Сети, с удивлением обнаружил наличие отсутствия каких-либо вменяемых материалов по теме. Вернее, материалы в Сети есть, но там больше вопросы с абстрактными ответами.
Все нижеприведённые примеры кода всего лишь примеры, хоть и взяты из рабочей конфигурации.
Для передачи данных на сервер методом POST в 1С Предприятии 8. X применяется метод HTTPСоединение
Прежде всего необходимо создать файл отправки данных (собственно содержимое POST-запроса) и, при необходимости, подготовить двоичные данные. Определяем разделитель разделов — границу boundary в POST-запросе в формате RFC (подробнее MIME: Организация данных ). В качестве границы может быть использована строка, состоящая из латинских букв и цифр. Чтобы ничего не выдумывать, воспользуемся штатным классом 1С Предприятия .
//Подготовка переменных
Boundary = СтрЗаменить(Строка(Новый УникальныйИдентификатор()), «-«, «»);
//Определяем имя файла ответа от веб-сервера.
ИмяФайлаОтвета = КаталогВременныхФайлов() + «answer.tmp»;
Содержимое файла ответа будет представлять собой какой-либо контент, отданный скриптом на стороне веб-сервера агенту пользователя. Подробнее смотрите пример скрипта php ниже.
Подготавливаем двоичные данные. В моём случае это файл архива zip, но может быть что угодно, хоть изображение.
//Какой-то ранее созданный файл с двоичными данными.
ИмяФайлаДляЗагрузки = «data.zip»;
Передача содержимого файла В 1С Предприятии отсутствуют средства чтения двоичных файлов в обычную строку, как в PHP или PERL. Одним из способов формирования строкового содержимого двоичного файла является кодирование с помощью штатных функций Base64Строка() или XMLСтрока(). !! Имейте в виду, кодирование по Base64 увеличивает размер передаваемых данных примерно на 30%. Декодировать содержимое файла на сервере можно при помощи функции php base64_decode() или аналогичных для используемого вами серверного ПО.
Другой способ состоит в использовании штатной функции (подробнее см. в СП). В этом случае формирование файла POST-запроса будет происходить немного сложнее. Оба способа описаны ниже.
Впрочем, двоичные данные лучше закодировать, иначе при приёме на веб-сервере файл может быть повреждён или не принят вовсе, если он будет содержать URL-значащие символы.
Следует также помнить об ограничениях хостинга и контролировать размер передаваемых данных. Как правило, для php это 2Мб. (см. файл /usr/local/php5/php.ini) ; Maximum allowed size for uploaded files. upload_max_filesize = 2M
В противном случе, на веб-сервере вы получите пустой POST-запрос.
Создаём файл отправки или содержимое POST-запроса.
Вариант с передачей кодированного файла.
Вариант с передачей обычного файла.
Формируем заголовок POST-запроса.
ЗаголовокHTTP = Новый Соответствие();
//Обязательные поля заголовка
//Укажем формат данных Content-Type
ЗаголовокHTTP. Вставить(«Content-Type», «multipart/form-data; boundary=» + Boundary);
//Укажем длину POST-запроса Content-Length
ФайлОтправки = Новый Файл(ИмяФайлаОтправки);
РазмерФайлаОтправки = XMLСтрока(ФайлОтправки. Размер());
ЗаголовокHTTP. Вставить(«Content-Length», РазмерФайлаОтправки);
//При необходимости зададим Referer, например таким образом
СтрокаСоединения = СтрокаСоединенияИнформационнойБазы();
СерверИсточник = НСтр(СтрокаСоединения, «Srvr») + НСтр(СтрокаСоединения, «Ref»);
ЗаголовокHTTP. Вставить(«Referer», СерверИсточник);
Если вы получите ошибку сервера вида Failed sending data to the peer, попробуйте убрать параметр Referer из заголовка. Инициализируем HTTPСоединение. При необходимости задаём параметры прокси.
СерверПриемник = «www.some_site.ru»; //Естественно, следует указать свой адрес.
Порт = «80»; //Это общепринятый порт. Возможно, для вашего сервера применяется другой. Конкретное значение уточните у хостера.
//ИспользоватьПрокси — какая-то логическая переменная, может быть значение флажка на форме или переключатель
Если ИспользоватьПрокси Тогда
Прокси = Новый ИнтернетПрокси;
Прокси. НеИспользоватьПроксиДляЛокальныхАдресов = Истина;
Прокси. Пароль = «ПарольПрокси»; // укажите своё значение
Прокси. Пользователь = «ПользовательПрокси»; // укажите своё значение
НТТР = Новый HTTPСоединение( СерверПриемник , Порт, , , Прокси);
Иначе
НТТР = Новый HTTPСоединение( СерверПриемник , Порт);
КонецЕсли;
Собственно, отправка данных серверу.
АдресСкрипта = «some_script.php»; //Естественно, следует указать имя своего скрипта.
Попытка
НТТР. ОтправитьДляОбработки(ИмяФайлаОтправки, АдресСкрипта, ИмяФайлаОтвета, ЗаголовокHTTP);
Исключение
//Пример обработки ошибки соединения.
#Если Клиент Тогда
Сообщить(«Неудачная попытка соединения: » + ОписаниеОшибки());
#Иначе
ЗаписьЖурналаРегистрации(«HTTPСоединение», УровеньЖурналаРегистрации. Ошибка, , , «Неудачная попытка соединения: » + ОписаниеОшибки());
#КонецЕсли
Возврат;
КонецПопытки;
//Удаляем файлы POST-запроса и фрагменты сообщения. Больше они не нужны
УдалитьФайлы(ИмяФайлаОтправки);
Если НЕ КодироватьФайл Тогда
Для каждого ЭлементМассива Из МассивФайловДляОбъединения Цикл
УдалитьФайлы(ЭлементМассива);
КонецЦикла;
КонецЕсли;
Отправили данные, анализируем ответ сервера.
Подробную реализацию отправки файла с кодированием и без кодирования смотрите в приложенной обработке Загрузка файлов на веб-сервер.epf.
И наконец, примерное содержимое скрипта-приёмника данных на веб-сервере «some_script.php». Скрипт вы также можете извлечь из макета обработки.
); } }
В завершение статьи процитирую.
Обращаю ваше внимание, что указанная здесь методика освещает возможность загрузки файлов на сервер. Использование их «в чистом виде» без доработки может быть небезопасным, и является потенциальной возможностью для взлома вашего сайта. А именно, отсутствие обработки имени файла, который пришел на сервер, отсутствие авторизации, определения источника, посылающего файл, может дать злоумышленнику возможность загрузить и выполнить вредоносный код на вашем сайте. Рассмотрение способов защиты не входит в данную статью и остается на ваше усмотрение.
Список использованной литературы:
CURL, application/x-www-form-urlencoded
// данные POST-запроса
‘Какое-то сообщение от пользователя’
CURLOPT_URL
CURLOPT_RETURNTRANSFER
CURLOPT_POST
CURLOPT_POSTFIELDS
CURLOPT_CONNECTTIMEOUT
Файл get.php на сервере http://server.com:
CURL, multipart/form-data
Важный момент: на форуме PHPCLUB. RU встретил упоминание, что может потребоваться указание полного пути файла — иначе CURL выдает ошибку.
Сокеты, application/x-www-form-urlencoded
// устанавливаем соединение с сервером
// содержимое файла
// данные POST-запроса
‘Какое-то сообщение от пользователя’
// заголовоки запроса
‘POST /get.php HTTP/1.1’
// отправляем запрос серверу
// получаем ответ
// закрываем соединение
// выводим ответ в браузер
Метод PUT
Описанные выше способы работают для относительно небольших файлов (примерно до 2-х мегабайт, для получения более точного значения необходимо смотреть в настройках PHP максимальный объем принимаемых данных методом POST). Чтобы обойти это ограничение, будем передавать файл методом PUT:
// Открываем передаваемый файл на чтение для дальнейшей его передачи
// Инициализируем сеанс CURL
// Указываем URL скрипта, который примет наш запрос. К имени скрипта
// добавляем еще две переменные, передаваемые методом GET
CURLOPT_URL ‘Какое-то сообщение от пользователя’
// Дескриптор файла, который собираемся передать
CURLOPT_INFILE
// Указываем размер отправляемого файла
CURLOPT_INFILESIZE
// Указываем, что файл передается методом PUT
CURLOPT_PUT
// Указываем, что будет производиться закачка на удаленный сервер
CURLOPT_UPLOAD
// Выполняем запрос CURL
// Завершаем сеанс CURL
Поиск:
$_FILES • $_POST • CURL • HTML • HTTP • PHP • POST • PUT • Web-разработка • application/x-www-form-urlencoded • boundary • move_uploaded_file • multipart/form-data • socket • Файл • сокет
Сокеты, multipart/form-data
// устанавливаем соединение с сервером
‘Какое-то сообщение от пользователя’
// содержимое файла
‘Content-Disposition: form-data; name=»name»‘
‘Content-Disposition: form-data; name=»message»‘
‘Content-Disposition: form-data; name=»upload»; filename=»image.jpg»‘
// пишем в сокет метод, URI и протокол
‘POST /get.php HTTP/1.1’
// а также имя хоста
// отправляем заголовки
‘Content-Type: multipart/form-data; boundary=’
// теперь передаем данные
// получаем ответ
// закрываем соединение
// выводим ответ в браузер

