При работе с внешними сервисами, использующими REST API, часто возникает необходимость отправки данных методом POST, включая не только текстовые параметры, но и файлы. Стандартные механизмы платформы 1С позволяют эффективно решить эту задачу, в том числе через создание собственной точки маршрутизации или API-шлюза, но требуют понимания принципов формирования HTTP-запросов, в частности, того, как реализовать POST-запрос multipart/form-data на 1С без использования внешних библиотек. В этом руководстве мы подробно разберем, как реализовать отправку HTTP POST-запроса с файлами из 1С v8.3 на управляемых формах.
Мы рассмотрим несколько подходов и дадим подробные примеры кода, чтобы вы могли адаптировать их под свои конкретные нужды. Основная сложность заключается в правильном формировании тела запроса, которое должно содержать границы между различными частями данных (файлами и текстовыми полями), а также соответствующим образом устанавливать заголовки запроса.
Прежде чем перейти к коду, давайте выясним, что такое multipart/form-data. Этот тип контента используется в HTTP-запросах, обычно при загрузке файлов через веб-формы. Изучив детальное руководство про отправку файлов на сервер через HTTP POST (multipart/form-data), становится понятно, что он позволяет объединять различные типы данных (текст, двоичные файлы) в одном теле запроса, разделяя их специальными "границами" (boundary). Если же ваша задача требует не HTTP, а прямой передачи данных на сервер, возможно, вам будет полезен протокол SFTP для 1С.
Каждая часть данных внутри multipart/form-data имеет свои заголовки, такие как Content-Disposition (указывающий имя поля и, если это файл, имя файла) и Content-Type (тип содержимого части, например, text/plain для текста или image/jpeg для изображения). Все это заключено между начальной и конечной границами, которые генерируются уникальным образом.
Например, структура такого запроса может выглядеть так:
--boundary_string
Content-Disposition: form-data; name="parameter1"
value1
--boundary_string
Content-Disposition: form-data; name="file1"; filename="example.txt"
Content-Type: text/plain
[содержимое файла example.txt]
--boundary_string--
В 1С нам нужно будет вручную сформировать это тело запроса.
Для реализации качественного обмена данными стоит изучить HTTP-запросы в 1С: пособие для начинающих и не только, а если вам нужно перенаправлять данные в реальном времени, обратите внимание на REST API To WebSocket прокси сервис. Для отправки запросов мы используем два основных объекта: HTTPСоединение и HTTPЗапрос. Объект HTTPСоединение отвечает за установление соединения с сервером, а HTTPЗапрос — за параметры самого запроса (метод, заголовки, тело). Давайте подготовим эти объекты.
Шаг 1: Создаем HTTPСоединение.
Для создания соединения нам потребуется адрес сервера, к которому мы будем обращаться. Если сервер использует HTTPS, необходимо указать соответствующий порт (обычно 443) и параметр ЗащищенноеСоединение.
// Пример URL: https://api.example.com/upload
// Разберем его на компоненты
Сервер = "api.example.com";
Путь = "/upload";
Порт = 443; // Стандартный порт для HTTPS
ЗащищенноеСоединение = Истина;
Попытка
HTTPСоединение = Новый HTTPСоединение(Сервер, Порт, , , , 10, ЗащищенноеСоединение);
Исключение Элемент
Сообщить("Ошибка при создании HTTPСоединения: " + Элемент.Описание);
Возврат;
КонецПопытки;
Шаг 2: Создаем HTTPЗапрос.
После создания соединения нам нужен объект HTTPЗапрос. В нем мы указываем метод (POST), путь к ресурсу на вервере и самое главное – заголовки запроса.
HTTPЗапрос = Новый HTTPЗапрос(Путь);
HTTPЗапрос.УстановитьМетод("POST");
Здесь важно правильно установить заголовок Content-Type, который должен быть multipart/form-data с указанием нашей уникальной границы (boundary). Границу мы сгенерируем динамически.
Это самый важный и детализированный шаг. Мы будем формировать тело запроса как строку, а затем преобразовывать ее в ДвоичныеДанные для передачи.
Шаг 3: Генерируем уникальный разделитель (boundary).
Разделитель должен быть уникальным, чтобы он не встречался в самих данных. Обычно используют комбинацию случайных символов и времени.
Boundary = СтрЗаменить(Новый УникальныйИдентификатор(), "-", ""); // Простой способ получить уникальную строку
Boundary = "---------------------------" + Boundary; // Добавим префикс для надежности
Шаг 4: Устанавливаем заголовок Content-Type.
Теперь, когда у нас есть Boundary, мы можем установить заголовок Content-Type для HTTPЗапрос.
HTTPЗапрос.Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + Boundary);
Шаг 5: Формируем тело запроса.
Тело запроса состоит из нескольких частей, каждая из которых начинается с -- + Boundary, содержит заголовки части и сами данные. Завершается запрос -- + Boundary + --.
Мы будем использовать объект ЗаписьТекста для эффективного формирования тела запроса, что часто требуется, когда выполняется работа с REST API: передача текстовых параметров и нескольких файлов в одном запросе. Перед тем как приступать к коду, рекомендуется выполнить предварительное тестирование API с помощью Postman, чтобы точно знать структуру ожидаемых сервером полей.
Давайте рассмотрим формирование тела запроса по шагам.
Для каждого текстового параметра нам потребуется отдельная часть в теле запроса.
ЗаписьТела = Новый ЗаписьТекста(Новый ЗаписьВременногоФайла(), КодировкаТекста.UTF8, Истина);
// Пример текстового поля "Идентификатор"
ЗаписьТела.ЗаписатьСтроку("--" + Boundary);
ЗаписьТела.ЗаписатьСтроку("Content-Disposition: form-data; name=""Идентификатор""");
ЗаписьТела.ЗаписатьСтроку(""); // Пустая строка для разделения заголовков части и ее данных
ЗаписьТела.ЗаписатьСтроку("НекийУникальныйИдентификатор123"); // Значение поля
// Пример текстового поля "Описание"
ЗаписьТела.ЗаписатьСтроку("--" + Boundary);
ЗаписьТела.ЗаписатьСтроку("Content-Disposition: form-data; name=""Описание""");
ЗаписьТела.ЗаписатьСтроку("");
ЗаписьТела.ЗаписатьСтроку("Описание файла для загрузки");
Обратите внимание на две обязательные пустые строки: одна после Content-Disposition, чтобы отделить заголовки части от ее данных, и еще одна после самих данных, перед следующей границей или завершением.
Для добавления файла потребуется прочитать его содержимое в ДвоичныеДанные.
// Пусть у нас есть путь к файлу
ПутьКФайлу = "C:\МоиДокументы\Отчет.pdf"; // Пример пути
ИмяФайла = "Отчет.pdf"; // Имя файла, которое будет видно на сервере
// Добавляем часть для файла
ЗаписьТела.ЗаписатьСтроку("--" + Boundary);
ЗаписьТела.ЗаписатьСтроку("Content-Disposition: form-data; name=""ФайлОтчета""; filename=""" + ИмяФайла + """");
// Можно указать Content-Type для файла, например, Content-Type: application/pdf
ЗаписьТела.ЗаписатьСтроку("Content-Type: application/pdf"); // Или определить по расширению файла
ЗаписьТела.ЗаписатьСтроку(""); // Обязательная пустая строка
ЗаписьТела.Записать(); // Закрываем запись для текущих текстовых частей
Теперь, когда текстовые части и заголовки файла записаны, нам нужно присоединить сами двоичные данные файла. Мы не можем просто записать их в ЗаписьТекста. Вместо этого, мы сначала собираем все текстовые части в одну ДвоичныеДанные, затем добавляем к ней ДвоичныеДанные файла, и так далее.
Для этого мы можем использовать объект ПотокВПамяти (в некоторых версиях 1С) или более универсальный подход с ЗаписьВременногоФайла и последовательным чтением/записью.
Универсальный подход для сбора всех частей:
ДвоичныеДанные и записываем его в поток.Рассмотрим более продвинутый и надежный способ формирования тела запроса с использованием ПотокВПамяти или временных файлов для обработки ДвоичныхДанных.
Мы будем накапливать все части тела запроса в список МассивДвоичныхДанных, а затем объединим их.
МассивДвоичныхДанных = Новый Массив();
Кодировка = КодировкаТекста.UTF8;
// Функция для преобразования строки в ДвоичныеДанные
Функция СтрокаВДвоичныеДанные(Знач СтрокаТекста, Знач Кодировка)
ЗаписьТекста = Новый ЗаписьТекста(Новый ЗаписьВременногоФайла(), Кодировка, Истина);
ЗаписьТекста.ЗаписатьСтроку(СтрокаТекста);
ЗаписьТекста.Закрыть();
Возврат Новый ДвоичныеДанные(ЗаписьТекста.ПолучитьИмяФайла());
КонецФункции;
// --- Добавление текстового поля "Идентификатор" ---
ЧастьСтрока = "--" + Boundary + Символы.ПС;
ЧастьСтрока = ЧастьСтрока + "Content-Disposition: form-data; name=""Идентификатор""" + Символы.ПС;
ЧастьСтрока = ЧастьСтрока + Символы.ПС; // Пустая строка
ЧастьСтрока = ЧастьСтрока + "НекийУникальныйИдентификатор123" + Символы.ПС;
МассивДвоичныхДанных.Добавить(СтрокаВДвоичныеДанные(ЧастьСтрока, Кодировка));
// --- Добавление текстового поля "Описание" ---
ЧастьСтрока = "--" + Boundary + Символы.ПС;
ЧастьСтрока = ЧастьСтрока + "Content-Disposition: form-data; name=""Описание""" + Символы.ПС;
ЧастьСтрока = ЧастьСтрока + Символы.ПС; // Пустая строка
ЧастьСтрока = ЧастьСтрока + "Описание файла для загрузки" + Символы.ПС;
МассивДвоичныхДанных.Добавить(СтрокаВДвоичныеДанные(ЧастьСтрока, Кодировка));
// --- Добавление файла ---
ПутьКФайлу = "C:\МоиДокументы\Отчет.pdf";
ИмяФайла = "Отчет.pdf";
ТипФайла = "application/pdf"; // Можно определить динамически по расширению
Если ФайлСуществует(ПутьКФайлу) Тогда
ЧастьСтрока = "--" + Boundary + Символы.ПС;
ЧастьСтрока = ЧастьСтрока + "Content-Disposition: form-data; name=""ФайлОтчета""; filename=""" + ИмяФайла + """" + Символы.ПС;
ЧастьСтрока = ЧастьСтрока + "Content-Type: " + ТипФайла + Символы.ПС;
ЧастьСтрока = ЧастьСтрока + Символы.ПС; // Пустая строка
МассивДвоичныхДанных.Добавить(СтрокаВДвоичныеДанные(ЧастьСтрока, Кодировка));
// Добавляем сам файл
МассивДвоичныхДанных.Добавить(Новый ДвоичныеДанные(ПутьКФайлу));
// После содержимого файла должна быть пустая строка
МассивДвоичныхДанных.Добавить(СтрокаВДвоичныеДанные(Символы.ПС, Кодировка));
Иначе
Сообщить("Файл не найден: " + ПутьКФайлу);
Возврат;
КонецЕсли;
// --- Добавление второго файла (если нужно) ---
ПутьКВторомуФайлу = "C:\МоиДокументы\Схема.png";
ИмяВторогоФайла = "Схема.png";
ТипВторогоФайла = "image/png";
Если ФайлСуществует(ПутьКВторомуФайлу) Тогда
ЧастьСтрока = "--" + Boundary + Символы.ПС;
ЧастьСтрока = ЧастьСтрока + "Content-Disposition: form-data; name=""Изображение""; filename=""" + ИмяВторогоФайла + """" + Символы.ПС;
ЧастьСтрока = ЧастьСтрока + "Content-Type: " + ТипВторогоФайла + Символы.ПС;
ЧастьСтрока = ЧастьСтрока + Символы.ПС; // Пустая строка
МассивДвоичныхДанных.Добавить(СтрокаВДвоичныеДанные(ЧастьСтрока, Кодировка));
// Добавляем сам файл
МассивДвоичныхДанных.Добавить(Новый ДвоичныеДанные(ПутьКВторомуФайлу));
// После содержимого файла должна быть пустая строка
МассивДвоичныхДанных.Добавить(СтрокаВДвоичныеДанные(Символы.ПС, Кодировка));
Иначе
Сообщить("Второй файл не найден: " + ПутьКВторомуФайлу);
// Продолжаем без этого файла, или прерываем
КонецЕсли;
// --- Завершающая часть ---
ЧастьСтрока = "--" + Boundary + "--" + Символы.ПС;
МассивДвоичныхДанных.Добавить(СтрокаВДвоичныеДанные(ЧастьСтрока, Кодировка));
// Объединяем все части в один ДвоичныеДанные
ТелоЗапроса = ОбщегоНазначенияКлиентСервер.ОбъединитьДвоичныеДанные(МассивДвоичныхДанных);
Функция ОбщегоНазначенияКлиентСервер.ОбъединитьДвоичныеДанные является удобным способом объединения массива ДвоичныхДанных, но если ее нет в вашей конфигурации, вы можете реализовать объединение вручную через ЗаписьДвоичныхДанных во временный файл.
Теперь, когда у нас сформировано тело запроса в виде ДвоичныеДанные, мы можем установить его в HTTPЗапрос и отправить, выполнив загрузку файлов из 1С на веб-сайт через HTTP POST.
Шаг 6: Установка тела запроса и отправка.
HTTPЗапрос.УстановитьТелоИзДвоичныхДанных(ТелоЗапроса);
Попытка
HTTPОтвет = HTTPСоединение.Отправить(HTTPЗапрос);
Исключение Элемент
Сообщить("Ошибка при отправке HTTP-запроса: " + Элемент.Описание);
Возврат;
КонецПопытки;
Шаг 7: Обработка ответа.
После получения ответа от сервера нам необходимо проверить его статус и, возможно, прочитать содержимое.
Если HTTPОтвет.КодСостояния = 200 Тогда // HTTP-статус OK
Сообщить("Запрос успешно отправлен. Код состояния: " + HTTPОтвет.КодСостояния);
ОтветСервера = HTTPОтвет.ПолучитьТелоКакСтроку();
Сообщить("Ответ сервера: " + ОтветСервера);
// Здесь можно разобрать ОтветСервера, если это JSON или XML
Иначе
Сообщить("Ошибка при отправке запроса. Код состояния: " + HTTPОтвет.КодСостояния);
Сообщить("Подробности ошибки: " + HTTPОтвет.ПолучитьТелоКакСтроку());
КонецЕсли;
Мы проверяем КодСостояния ответа. Обычно 200 OK означает успешное выполнение. Если сервер вернул другой код, например, 4xx или 5xx, это указывает на ошибку — для этого есть логирование и анализ HTTP-запросов для 1С. В этом случае мы можем прочитать тело ответа, чтобы получить детали ошибки от сервера.
КодировкаТекста.UTF8) соответствует ожиданиям сервера.Content-Type для каждого загружаемого файла.HTTPСоединение, если передаете большие файлы.Давайте сведем все воедино в один функциональный блок, который можно вызвать из серверного модуля.
&НаСервере
Функция ОтправитьHTTPPOSTСФайлами(ПутьКФайлу1, ИмяФайла1, ПутьКФайлу2, ИмяФайла2, ПараметрыЗапроса)
Сервер = "api.example.com";
Путь = "/upload";
Порт = 443;
ЗащищенноеСоединение = Истина;
Таймаут = 60;
Попытка
HTTPСоединение = Новый HTTPСоединение(Сервер, Порт, , , , Таймаут, ЗащищенноеСоединение);
Исключение Элемент
Возврат "Ошибка при создании HTTPСоединения: " + Элемент.Описание;
КонецПопытки;
HTTPЗапрос = Новый HTTPЗапрос(Путь);
HTTPЗапрос.УстановитьМетод("POST");
Boundary = СтрЗаменить(Новый УникальныйИдентификатор(), "-", "");
Boundary = "---------------------------" + Boundary;
HTTPЗапрос.Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + Boundary);
МассивДвоичныхДанных = Новый Массив();
Кодировка = КодировкаТекста.UTF8;
// Вспомогательная функция для формирования ДД из строки
// (реализация приведена в тексте статьи выше)
// Добавляем текстовые поля из ПараметрыЗапроса
Для Каждого ЭлементПараметра Из ПараметрыЗапроса Цикл
ЧастьСтрока = "--" + Boundary + Символы.ПС;
ЧастьСтрока = ЧастьСтрока + "Content-Disposition: form-data; name=""" + ЭлементПараметра.Ключ + """" + Символы.ПС;
ЧастьСтрока = ЧастьСтрока + Символы.ПС;
ЧастьСтрока = ЧастьСтрока + Строка(ЭлементПараметра.Значение) + Символы.ПС;
МассивДвоичныхДанных.Добавить(СтрокаВДвоичныеДанные(ЧастьСтрока, Кодировка));
КонецЦикла;
// Добавление файлов в массив (аналогично примеру выше)
// ...
// Завершающая часть
ЧастьСтрока = "--" + Boundary + "--" + Символы.ПС;
МассивДвоичныхДанных.Добавить(СтрокаВДвоичныеДанные(ЧастьСтрока, Кодировка));
ТелоЗапроса = ОбщегоНазначенияКлиентСервер.ОбъединитьДвоичныеДанные(МассивДвоичныхДанных);
HTTPЗапрос.УстановитьТелоИзДвоичныхДанных(ТелоЗапроса);
Попытка
HTTPОтвет = HTTPСоединение.Отправить(HTTPЗапрос);
Исключение Элемент
Возврат "Ошибка при отправке HTTP-запроса: " + Элемент.Описание;
КонецПопытки;
Если HTTPОтвет.КодСостояния = 200 Или HTTPОтвет.КодСостояния = 201 Тогда
Возврат "Запрос успешно отправлен. Код: " + HTTPОтвет.КодСостояния;
Иначе
Возврат "Ошибка при отправке. Код: " + HTTPОтвет.КодСостояния;
КонецЕсли;
КонецФункции
Мы видим, что такой подход позволяет гибко добавлять любое количество текстовых полей и файлов, полностью контролируя структуру multipart/form-data. Теперь вы вооружены знаниями и примерами для успешной реализации отправки HTTP POST-запросов с файлами из вашей конфигурации 1С.