Как выполнить HTTP POST запрос в 1С с передачей файла multipart/form-data и использованием клиентского сертификата?

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

Мы сталкиваемся с задачей интеграции 1С с внешними системами, которая требует выполнения сложных HTTP-запросов. Один из распространенных сценариев — отправка файлов на сервер с использованием метода POST, формата `multipart/form-data` и дополнительной аутентификации через заголовки, а также с обязательным использованием клиентского сертификата для установки защищенного соединения HTTPS. Рассмотрим, как перевести типичный `curl`-запрос в рабочий код на платформе 1С.

Анализ curl-запроса и требований

Прежде чем приступить к написанию кода в 1С, давайте внимательно проанализируем исходный `curl`-запрос. Это поможет нам понять все его компоненты и требования к формированию HTTP-запроса в 1С. Пример `curl`-запроса:


curl -i -X POST \
  -H "Content-Type:multipart/form-data" \
  -H "X-auth-Subscriber:501" \
  -H "X-Auth-Group:GROUP" \
  -H "X-Auth-Username:TestUsr" \
  -H "X-Auth-Password:usrte-s@34" \
  -F "file=@\"./CHT_00501_UCH_03-00_20250402154720000.zip.pem.pem\";filename=\"CHT_00501_UCH_03-00_20250402154720000.zip.pem.pem\"" \
  'https://your.server.com/api/upload'

Разберем ключевые элементы этого запроса:

  1. -X POST: Это указывает на использование метода POST. В 1С мы установим свойство Метод объекта HTTPЗапрос в "POST".
  2. -H "Content-Type:multipart/form-data": Этот заголовок указывает, что тело запроса будет содержать данные, разделенные границей (boundary), и может включать файлы и другие поля формы. В 1С мы должны будем сформировать этот заголовок вручную, указав уникальную границу, например, Content-Type: multipart/form-data; boundary=----------WebKitFormBoundary1234567890.
  3. -H "X-auth-Subscriber:501", -H "X-Auth-Group:GROUP", -H "X-Auth-Username:TestUsr", -H "X-Auth-Password:usrte-s@34": Это пользовательские заголовки аутентификации. В 1С их необходимо добавить в коллекцию Заголовки объекта HTTPЗапрос.
  4. -F "file=@\"./CHT_00501_UCH_03-00_20250402154720000.zip.pem.pem\";filename=\"CHT_00501_UCH_03-00_20250402154720000.zip.pem.pem\"": Этот параметр указывает на отправку файла.
    • file: Имя поля формы, под которым файл будет передан на сервер.
    • @\"./CHT_00501_UCH_03-00_20250402154720000.zip.pem.pem\": Путь к файлу на локальной машине.
    • filename=\"CHT_00501_UCH_03-00_20250402154720000.zip.pem.pem\": Имя файла, которое будет отображаться на сервере.
    Для этого нам потребуется прочитать файл в 1С как ДвоичныеДанные, затем закодировать его в Base64 и включить в тело multipart/form-data запроса с соответствующими заголовками Content-Disposition и Content-Type для этой части.
  5. 'https://your.server.com/api/upload': Это URL конечной точки, куда отправляется запрос. Важно всегда указывать полный и корректный URL, иначе запрос не будет отправлен. В 1С это будет адрес для объекта HTTPЗапрос.
  6. Передача сертификата: Хотя в `curl`-запросе напрямую не указан параметр для клиентского сертификата (обычно это `-E` или `--cert`), в 1С для работы с HTTPS, особенно если сервер требует клиентский сертификат для аутентификации, нам потребуется настроить защищенное соединение с использованием объекта ЗащищенноеСоединениеOpenSSL.

Подготовка к реализации в 1С

Прежде чем писать код, убедимся, что у нас есть все необходимые компоненты и данные:

  1. Адрес сервера (URL): Полный адрес, куда будет отправлен запрос (например, `https://your.server.com/api/upload`).
  2. Путь к файлу: Локальный путь к файлу, который необходимо отправить (например, `C:\Files\CHT_00501_UCH_03-00_20250402154720000.zip.pem.pem`).
  3. Имя файла для сервера: Имя, под которым файл будет принят на сервере (обычно совпадает с локальным именем, но может отличаться).
  4. Данные аутентификации: Значения для заголовков `X-auth-Subscriber`, `X-Auth-Group`, `X-Auth-Username`, `X-Auth-Password`.
  5. Клиентский сертификат: Файл сертификата (например, в формате PEM, PFX) и, если требуется, пароль к нему. Убедитесь, что сертификат доступен и корректен.

Пошаговая реализация HTTP POST запроса в 1С

Давайте построим функцию в 1С, которая будет выполнять этот HTTP-запрос.

Шаг 1: Создание объекта HTTPЗапрос

Мы начинаем с создания экземпляра объекта HTTPЗапрос, указывая полный URL и метод запроса.


// Настройки подключения
АдресСервера = "https://your.server.com/api/upload"; // Укажите актуальный URL
ПутьКФайлу = "C:\CHT_00501_UCH_03-00_20250402154720000.zip.pem.pem"; // Укажите полный путь к файлу
ИмяФайлаДляСервера = "CHT_00501_UCH_03-00_20250402154720000.zip.pem.pem"; // Имя файла, под которым он будет на сервере

// Создание HTTP-запроса
ЗапросHTTP = Новый HTTPЗапрос(АдресСервера);
ЗапросHTTP.Метод = "POST";

Мы определяем АдресСервера как полную ссылку на ресурс, включая протокол и путь. Свойство Метод устанавливается в "POST" в соответствии с нашим `curl`-запросом.

Шаг 2: Установка заголовков запроса

Далее нам необходимо добавить пользовательские заголовки аутентификации, которые были в `curl`-запросе.


// Установка заголовков аутентификации
ЗапросHTTP.Заголовки.Вставить("X-auth-Subscriber", "501");
ЗапросHTTP.Заголовки.Вставить("X-Auth-Group", "GROUP");
ЗапросHTTP.Заголовки.Вставить("X-Auth-Username", "TestUsr");
ЗапросHTTP.Заголовки.Вставить("X-Auth-Password", "usrte-s@34");

Мы используем коллекцию Заголовки объекта HTTPЗапрос и метод Вставить() для добавления каждого заголовка и его значения.

Шаг 3: Формирование тела запроса для multipart/form-data

Это самый сложный и критически важный шаг, поскольку 1С не имеет встроенной функции для автоматического формирования `multipart/form-data`. Мы должны создать тело запроса вручную, соблюдая спецификацию `multipart/form-data`. 1. **Генерация границы (boundary)**: Создадим уникальную строку, которая будет разделять части данных в теле запроса. Это может быть любой уникальный набор символов, но часто используют что-то вроде `----------WebKitFormBoundary` с добавлением случайных символов или уникального идентификатора.


// Подготовка multipart/form-data
Граница = "----------WebKitFormBoundary" + Строка(Новый УникальныйИдентификатор);
ЗапросHTTP.Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + Граница);

Важно: Заголовок Content-Type для всего запроса должен содержать созданную нами границу. 2. **Чтение файла**: Прочитаем содержимое файла, который хотим отправить, в двоичные данные.


// Чтение файла
ДанныеФайла = Новый ДвоичныеДанные(ПутьКФайлу);

3. **Формирование частей тела запроса**: Каждая часть `multipart/form-data` начинается с `boundary`, за которым следуют заголовки `Content-Disposition`, `Content-Type` (для конкретной части) и затем сами данные. Завершается тело запроса двойным `--` после последней границы.


// Формирование тела запроса
ТелоЗапроса = "";

// Добавление части для файла
ТелоЗапроса = ТелоЗапроса + "--" + Граница + Символы.ПС; // Начало части с границей
ТелоЗапроса = ТелоЗапроса + "Content-Disposition: form-data; name=""file""; filename=""" + ИмяФайлаДляСервера + """" + Символы.ПС; // Заголовок Content-Disposition для файла, где "file" - это имя поля
ТелоЗапроса = ТелоЗапроса + "Content-Type: application/octet-stream" + Символы.ПС + Символы.ПС; // Content-Type для файла (бинарные данные), затем пустая строка
ТелоЗапроса = ТелоЗапроса + ДанныеФайла.ПолучитьBase64Строку() + Символы.ПС; // Данные файла, закодированные в Base64, затем перенос строки

// Если есть другие поля формы, их можно добавить аналогично
// Например, для текстового поля "description":
// ТелоЗапроса = ТелоЗапроса + "--" + Граница + Символы.ПС;
// ТелоЗапроса = ТелоЗапроса + "Content-Disposition: form-data; name=""description""" + Символы.ПС + Символы.ПС;
// ТелоЗапроса = ТелоЗапроса + "Это описание файла." + Символы.ПС;

// Завершающая граница
ТелоЗапроса = ТелоЗапроса + "--" + Граница + "--" + Символы.ПС;

// Установка сформированного тела запроса
ЗапросHTTP.УстановитьТелоИзСтроки(ТелоЗапроса, "text/plain; charset=UTF-8");

Здесь мы: * Добавляем `Content-Disposition` для файла, указывая `name` (имя поля формы, `file` в нашем случае) и `filename` (имя файла для сервера). * Указываем `Content-Type: application/octet-stream` для бинарных данных файла. * Конвертируем ДвоичныеДанные файла в строку Base64 с помощью ПолучитьBase64Строку(). * Завершаем тело запроса последней границей с двумя дефисами (`--`) в конце. * Устанавливаем сформированную строку в качестве тела запроса с кодировкой UTF-8.

Шаг 4: Настройка защищенного соединения HTTPS с клиентским сертификатом

Для установки защищенного соединения HTTPS, особенно когда требуется клиентский сертификат, недостаточно просто передать структуру с сертификатом в параметры СоединениеHTTP. Мы должны использовать более специализированные объекты 1С: ЗащищенноеСоединениеOpenSSL и HTTPСоединение. 1. **Загрузка сертификата**: Предположим, что наш сертификат находится в файле. Нам нужна функция, которая загрузит его.


// Функция для получения сертификата из файла (пример)
Функция ПолучитьСертификатИзФайла(ПутьКСертификату, ПарольСертификата = "")

    Если Не СтрДлина(ПутьКСертификату) > 0 Тогда
        Сообщить("Не указан путь к файлу сертификата.");
        Возврат Неопределено;
    КонецЕсли;

    Попытка
        // В зависимости от формата сертификата (PEM, PFX и т.д.)
        // Используем подходящий метод загрузки.
        // Для PFX файла с паролем:
        Если ПарольСертификата = "" Тогда
             Сертификат = Новый СертификатOpenSSL(ПутьКСертификату); // Для PEM без пароля или PFX без пароля
        Иначе
             Сертификат = Новый СертификатOpenSSL(ПутьКСертификату, ПарольСертификата); // Для PFX с паролем
        КонецЕсли;

        Возврат Сертификат;
    Исключение
        Сообщить("Ошибка загрузки сертификата: " + ОписаниеОшибки());
        Возврат Неопределено;
    КонецПопытки;

КонецФункции

Например, если у вас сертификат в формате `PEM` без пароля, вы можете использовать `Новый СертификатOpenSSL(ПутьКСертификату)`. Если это `PFX` файл с паролем, то `Новый СертификатOpenSSL(ПутьКСертификату, ПарольСертификата)`. 2. **Создание защищенного соединения**: Мы создаем объект ЗащищенноеСоединениеOpenSSL, передаем ему наш сертификат, а затем используем это защищенное соединение при создании HTTPСоединения.


// Настройка HTTPS с клиентским сертификатом
ПутьКСертификату = "C:\my_certificate.pem"; // Укажите актуальный путь к вашему клиентскому сертификату
ПарольСертификата = ""; // Если сертификат защищен паролем, укажите его

КлиентскийСертификат = ПолучитьСертификатИзФайла(ПутьКСертификату, ПарольСертификата);

Если КлиентскийСертификат = Неопределено Тогда
    Сообщить("Не удалось получить клиентский сертификат. Отправка запроса невозможна.");
    Возврат Неопределено;
КонецЕсли;

ПараметрыЗащищенногоСоединения = Новый Структура;
ПараметрыЗащищенногоСоединения.Вставить("ПроверятьСервер", Истина); // Рекомендуется проверять сертификат сервера
ПараметрыЗащищенногоСоединения.Вставить("КлиентскийСертификат", КлиентскийСертификат); // Передаем наш клиентский сертификат

ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL(ПараметрыЗащищенногоСоединения);

// Создание и настройка HTTP-соединения
// АдресСервера должен быть "https://your.server.com" без пути
// Путь запроса будет задаваться в HTTPЗапросе
АдресХоста = URI(АдресСервера).Хост; // Получаем только хост из полного URL
ПортХоста = URI(АдресСервера).Порт; // Получаем порт из полного URL (обычно 443 для HTTPS)

СоединениеHTTP = Новый HTTPСоединение(АдресХоста, ПортХоста, , , , , ЗащищенноеСоединение);

Здесь URI(АдресСервера).Хост используется для извлечения имени хоста из полного URL, а ЗащищенноеСоединение передается как последний параметр при создании HTTPСоединения. Параметр ПроверятьСервер рекомендуется установить в Истина для обеспечения безопасности.

Шаг 5: Отправка запроса и обработка ответа

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


// Отправка запроса
Попытка
    ОтветHTTP = СоединениеHTTP.Отправить(ЗапросHTTP);
    
    Результат = ОтветHTTP.ПолучитьТелоКакСтроку(); // Или ПолучитьТелоКакДвоичныеДанные() если ожидается бинарный ответ

    Если ОтветHTTP.КодСостояния = 200 Тогда
        Сообщить("Файл успешно отправлен. Код состояния: " + ОтветHTTP.КодСостояния);
        Сообщить("Ответ сервера: " + Результат);
    Иначе
        Сообщить("Ошибка при отправке файла. Код состояния: " + ОтветHTTP.КодСостояния);
        Сообщить("Ответ сервера: " + Результат);
    КонецЕсли;

    Возврат ОтветHTTP;
Исключение
    Сообщить("Ошибка при отправке запроса: " + ОписаниеОшибки());
    Возврат Неопределено;
КонецПопытки;

Мы используем блок Попытка...Исключение для корректной обработки возможных ошибок сети или сервера. После отправки запроса мы проверяем КодСостояния ответа. Код 200 обычно означает успех. Тело ответа можно получить с помощью ПолучитьТелоКакСтроку() или ПолучитьТелоКакДвоичныеДанные(), в зависимости от ожидаемого формата.

Полный пример кода

Объединим все шаги в одну функцию, которая будет отправлять файл.


// Функция для получения сертификата из файла
Функция ПолучитьСертификатИзФайла(ПутьКСертификату, ПарольСертификата = "")

    Если Не СтрДлина(ПутьКСертификату) > 0 Тогда
        Сообщить("Не указан путь к файлу сертификата.");
        Возврат Неопределено;
    КонецЕсли;

    Попытка
        Перем Сертификат;
        Если ПарольСертификата = "" Тогда
             Сертификат = Новый СертификатOpenSSL(ПутьКСертификату); // Для PEM без пароля или PFX без пароля
        Иначе
             Сертификат = Новый СертификатOpenSSL(ПутьКСертификату, ПарольСертификата); // Для PFX с паролем
        КонецЕсли;

        Возврат Сертификат;
    Исключение
        Сообщить("Ошибка загрузки сертификата: " + ОписаниеОшибки());
        Возврат Неопределено;
    КонецПопытки;

КонецФункции

// Основная функция для отправки файла
Функция ОтправитьФайлНаСерверССертификатом() Экспорт

    // 1. Настройки подключения и файлов
    АдресСервераПолный = "https://your.server.com/api/upload"; // !!! Укажите полный URL вашего сервера
    ПутьКФайлуДляОтправки = "C:\CHT_00501_UCH_03-00_20250402154720000.zip.pem.pem"; // !!! Укажите полный путь к файлу
    ИмяФайлаДляСервера = "CHT_00000_UCH_03-00_20250402154720000.zip.pem.pem"; // Имя файла, под которым он будет принят сервером

    ПутьККлиентскомуСертификату = "C:\my_client_cert.pem"; // !!! Укажите путь к вашему клиентскому сертификату
    ПарольКлиентскогоСертификата = ""; // Укажите пароль, если он есть, иначе оставьте пустым

    // 2. Создание объекта HTTPЗапрос
    ЗапросHTTP = Новый HTTPЗапрос(АдресСервераПолный);
    ЗапросHTTP.Метод = "POST";

    // 3. Установка заголовков аутентификации
    ЗапросHTTP.Заголовки.Вставить("X-auth-Subscriber", "501");
    ЗапросHTTP.Заголовки.Вставить("X-Auth-Group", "GROUP");
    ЗапросHTTP.Заголовки.Вставить("X-Auth-Username", "TestUsr");
    ЗапросHTTP.Заголовки.Вставить("X-Auth-Password", "usrte-s@34");

    // 4. Формирование тела запроса для multipart/form-data
    Граница = "----------WebKitFormBoundary" + Строка(Новый УникальныйИдентификатор);
    ЗапросHTTP.Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + Граница);

    Если Не ФайлСуществует(ПутьКФайлуДляОтправки) Тогда
        Сообщить("Файл для отправки не найден по пути: " + ПутьКФайлуДляОтправки);
        Возврат Неопределено;
    КонецЕсли;

    ДанныеФайла = Новый ДвоичныеДанные(ПутьКФайлуДляОтправки);

    ТелоЗапроса = "";
    ТелоЗапроса = ТелоЗапроса + "--" + Граница + Символы.ПС;
    ТелоЗапроса = ТелоЗапроса + "Content-Disposition: form-data; name=""file""; filename=""" + ИмяФайлаДляСервера + """" + Символы.ПС;
    ТелоЗапроса = ТелоЗапроса + "Content-Type: application/octet-stream" + Символы.ПС + Символы.ПС;
    ТелоЗапроса = ТелоЗапроса + ДанныеФайла.ПолучитьBase64Строку() + Символы.ПС;
    ТелоЗапроса = ТелоЗапроса + "--" + Граница + "--" + Символы.ПС;

    ЗапросHTTP.УстановитьТелоИзСтроки(ТелоЗапроса, "text/plain; charset=UTF-8");

    // 5. Настройка защищенного соединения HTTPS с клиентским сертификатом
    КлиентскийСертификат = ПолучитьСертификатИзФайла(ПутьККлиентскомуСертификату, ПарольКлиентскогоСертификата);

    Если КлиентскийСертификат = Неопределено Тогда
        Возврат Неопределено;
    КонецЕсли;

    ПараметрыЗащищенногоСоединения = Новый Структура;
    ПараметрыЗащищенногоСоединения.Вставить("ПроверятьСервер", Истина);
    ПараметрыЗащищенногоСоединения.Вставить("КлиентскийСертификат", КлиентскийСертификат);

    ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL(ПараметрыЗащищенногоСоединения);

    // Извлекаем хост и порт из полного URL для HTTPСоединения
    УРИ = Новый УРИ(АдресСервераПолный);
    АдресХоста = УРИ.Хост;
    ПортХоста = УРИ.Порт;
    Если ПортХоста = 0 Тогда // Если порт не указан в URI, используем стандартный для HTTPS
        ПортХоста = 443;
    КонецЕсли;

    СоединениеHTTP = Новый HTTPСоединение(АдресХоста, ПортХоста, , , , , ЗащищенноеСоединение);

    // 6. Отправка запроса и обработка ответа
    Попытка
        ОтветHTTP = СоединениеHTTP.Отправить(ЗапросHTTP);
        
        Результат = ОтветHTTP.ПолучитьТелоКакСтроку();

        Если ОтветHTTP.КодСостояния = 200 Тогда
            Сообщить("Файл успешно отправлен. Код состояния: " + ОтветHTTP.КодСостояния);
            Сообщить("Ответ сервера: " + Результат);
        Иначе
            Сообщить("Ошибка при отправке файла. Код состояния: " + ОтветHTTP.КодСостояния);
            Сообщить("Ответ сервера: " + Результат);
        КонецЕсли;

        Возврат ОтветHTTP;
    Исключение
        Сообщить("Ошибка при отправке запроса: " + ОписаниеОшибки());
        Возврат Неопределено;
    КонецПопытки;

КонецФункции

Отладка и диагностика

При работе со сложными HTTP-запросами и интеграциями возникают ошибки. Вот несколько советов по отладке:

  1. Используйте инструменты перехвата трафика (Fiddler, Wireshark, Postman):

    Установите программу Fiddler (или аналогичную) на вашей машине. Она позволяет перехватывать и анализировать весь HTTP/HTTPS трафик, проходящий через ваш компьютер. С помощью Fiddler вы можете:

    1. Выполнить ваш исходный `curl`-запрос (или запрос через Postman), а затем посмотреть его RAW-представление в Fiddler. Это покажет точную структуру заголовков, границ, кодировку и содержимое тела запроса, которое сервер ожидает.
    2. Запустить ваш код в 1С и также перехватить запрос в Fiddler.
    3. Сравнить RAW-запросы: Сопоставьте запрос, отправленный `curl`, с запросом, отправленным из 1С. Ищите любые различия в заголовках, границах, кодировках, последовательности частей `multipart/form-data` или наличии лишних символов (например, лишних пробелов или переносов строк). Это самый эффективный способ найти причину расхождений.
  2. Проверка URL: Убедитесь, что полный URL в HTTPЗапрос указан корректно, включая протокол (`http://` или `https://`), домен, порт (если он нестандартный) и путь к ресурсу. Ошибки типа "no URL specified" (как в сообщении 3) часто возникают из-за неполного или отсутствующего URL.
  3. Кодировка тела запроса: Убедитесь, что кодировка, указанная в УстановитьТелоИзСтроки(ТелоЗапроса, "text/plain; charset=UTF-8"), соответствует фактической кодировке вашего тела запроса. Для `multipart/form-data` с Base64 содержимым файла, `UTF-8` обычно подходит.
  4. Сертификаты: Проверьте, что путь к файлу сертификата и пароль (если есть) указаны правильно. Убедитесь, что сам сертификат является действительным и доверенным для целевого сервера. Если вы используете тестовые сертификаты, возможно, потребуется отключить строгую проверку сертификата сервера (хотя это не рекомендуется для продуктивных систем).
  5. Логирование: Добавляйте подробное логирование (по аналогии с тестированием запросов к ВетИС), чтобы отслеживать значения заголовков, формируемое тело запроса и ответ сервера — для этого подойдёт инструмент логирования и анализа HTTP-запросов в 1С.

Следуя этим шагам и используя методы отладки, мы сможем успешно реализовать сложный HTTP POST запрос в 1С и наладить интеграцию с внешними системами. Для интерактивной проверки и отладки REST API непосредственно в интерфейсе платформы можно использовать обработку для тестирования HTTP-запросов в 1С.

← На главную