Как правильно отправить HTTP POST-запрос с файлами (вложениями) и текстовыми параметрами из 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При работе с внешними сервисами, использующими REST API, часто возникает необходимость отправки данных методом POST, включая не только текстовые параметры, но и файлы. Стандартные механизмы платформы 1С позволяют эффективно решить эту задачу, в том числе через создание собственной точки маршрутизации или API-шлюза, но требуют понимания принципов формирования HTTP-запросов, в частности, того, как реализовать POST-запрос multipart/form-data на 1С без использования внешних библиотек. В этом руководстве мы подробно разберем, как реализовать отправку HTTP POST-запроса с файлами из 1С v8.3 на управляемых формах.

Мы рассмотрим несколько подходов и дадим подробные примеры кода, чтобы вы могли адаптировать их под свои конкретные нужды. Основная сложность заключается в правильном формировании тела запроса, которое должно содержать границы между различными частями данных (файлами и текстовыми полями), а также соответствующим образом устанавливать заголовки запроса.

Понимание формата Multipart/form-data

Прежде чем перейти к коду, давайте выясним, что такое 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-запроса

Для реализации качественного обмена данными стоит изучить 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). Границу мы сгенерируем динамически.

Формирование тела запроса multipart/form-data

Это самый важный и детализированный шаг. Мы будем формировать тело запроса как строку, а затем преобразовывать ее в ДвоичныеДанные для передачи.

Шаг 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С) или более универсальный подход с ЗаписьВременногоФайла и последовательным чтением/записью.

Универсальный подход для сбора всех частей:

  1. Создаем временный файл для записи всех частей.
  2. Записываем текстовые части и заголовки файлов.
  3. После каждого блока "заголовки части + пустая строка", считываем файл в ДвоичныеДанные и записываем его в поток.
  4. В конце добавляем завершающую границу.

Рассмотрим более продвинутый и надежный способ формирования тела запроса с использованием ПотокВПамяти или временных файлов для обработки ДвоичныхДанных.

Мы будем накапливать все части тела запроса в список МассивДвоичныхДанных, а затем объединим их.


МассивДвоичныхДанных = Новый Массив();
Кодировка = КодировкаТекста.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С. В этом случае мы можем прочитать тело ответа, чтобы получить детали ошибки от сервера.

Важные замечания и рекомендации

  1. Кодировка: Убедитесь, что кодировка текста (КодировкаТекста.UTF8) соответствует ожиданиям сервера.
  2. Размер файлов: При работе с очень большими файлами стоит рассмотреть возможность асинхронной отправки.
  3. Заголовки: Некоторые сервисы могут требовать специфичные заголовки, такие как Authorization. Например, для работы с некоторыми магазинами приложений может потребоваться получение токена авторизации RuStore с подписью RSA.
  4. Определение MIME-типа файла: Желательно указывать правильный Content-Type для каждого загружаемого файла.
  5. Таймаут: Увеличьте его в конструкторе HTTPСоединение, если передаете большие файлы.
  6. Работа с HTTPS-сертификатами: Если сервер использует защищенное соединение, убедитесь, что сертификаты корректно обрабатываются платформой.

Пример полного кода (серверный модуль)

Давайте сведем все воедино в один функциональный блок, который можно вызвать из серверного модуля.


&НаСервере
Функция Отправить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С.

← На главную