Как отправить multipart/form-data запрос с файлом и параметрами через WinHttp в 1С 7.7?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

При интеграции устаревших систем на платформе 1С:Предприятие 7.7 с современными веб-сервисами и REST API разработчики часто сталкиваются с серьезными трудностями. Одной из наиболее распространенных задач является отправка файлов (например, фотографий пользователей, сканов документов или отчетов) вместе с сопутствующими текстовыми параметрами (такими как идентификаторы пользователей uid, токены или метаданные). Для решения широкого спектра задач интеграции со сторонними веб-сервисами также полезно изучить методы отправки API-запросов из 1С 7.7, которые позволяют обходить ограничения старых версий операционных систем.

В современных веб-технологиях стандартом де-факто для передачи смешанных данных (текста и бинарных файлов) является тип контента multipart/form-data. Однако при попытке реализовать такой запрос в 1С 7.7 с использованием стандартного системного объекта WinHttp.WinHttpRequest.5.1 программисты совершают критические ошибки, которые приводят к сбоям с кодами ответов 400 Bad Request, 403 Forbidden или системным исключениям вида «Невозможно создать файл, так как он уже существует».

В этой статье мы подробно проанализируем, почему возникает данная проблема, в чем заключается главная ошибка автора темы на форуме, и разберем готовое техническое решение, позволяющее обойти ограничения платформы 1С 7.7 и сформировать корректный HTTP-запрос без использования сторонних утилит вроде curl.

В чем заключается главная ошибка при работе с WinHttp?

Давайте проанализируем исходный код автора темы. Пытаясь отправить идентификатор пользователя uid и файл картинки, автор выполняет два последовательных вызова метода отправки данных:


WinHttp.Send(uid); // Отправка UID
WinHttp.Send(file); // Отправка файла (до этой строки код даже не доходит)

Это принципиальная и самая важная ошибка. Метод Send() объекта WinHttp.WinHttpRequest.5.1 предназначен для отправки **всего тела запроса за один раз**. Как только вы вызываете метод Send(), объект завершает формирование запроса, отправляет его на сервер и переходит в режим ожидания ответа. Повторный вызов Send() на том же экземпляре объекта либо вызовет ошибку выполнения (так как транзакция уже закрыта), либо инициализирует совершенно новый, изолированный HTTP-запрос.

В утилите командной строки curl параметры типа -F 'uid=...' и -F 'file=@...' указывают программе на то, что нужно взять все эти разнородные данные, самостоятельно упаковать их в один общий пакет (тело запроса), разделить специальными границами (boundary) и отправить одним единственным пакетом POST. В объекте WinHttp автоматического упаковщика нет — всю структуру multipart-запроса нам придется собирать вручную.

Разбор структуры запроса multipart/form-data

Чтобы сервер смог корректно распарсить наш запрос, мы должны сформировать его тело в строгом соответствии со стандартом HTTP. Давайте разберем, как выглядит сырое тело multipart-запроса «под капотом»:


------BoundaryString
Content-Disposition: form-data; name="uid"

C83FD6CC59994F2717F7AFDBE428FE2F
------BoundaryString
Content-Disposition: form-data; name="file"; filename="photo.jpg"
Content-Type: image/jpeg

[Сырые двоичные данные файла изображения]
------BoundaryString--

Рассмотрим ключевые особенности этой структуры:

  1. Граница (Boundary): Это уникальная текстовая строка (например, ----BoundaryString), которая не должна встречаться внутри передаваемых данных. Она отделяет один параметр от другого. Граница обязательно указывается в заголовке запроса Content-Type.
  2. Переносы строк: Каждая строка в разметке тела запроса должна строго завершаться парой управляющих символов CRLF. В языке 1С 7.7 это реализуется как Симв(13) + Симв(10).
  3. Формат полей: Каждое поле начинается с префикса границы с двумя дефисами в начале (------BoundaryString), затем идут заголовки поля (например, Content-Disposition), пустая строка (двойной CRLF) и само значение параметра.
  4. Финальная граница: Запрос обязательно должен заканчиваться закрывающей границей, которая имеет два дефиса как в начале, так и в конце: ------BoundaryString--.

Проблема обработки бинарных данных в 1С 7.7

Казалось бы, мы можем просто собрать всё тело запроса как обычную строку в 1С 7.7, склеив текст разметки с прочитанным файлом картинки, и передать эту строку в метод Send(). Однако здесь мы сталкиваемся с жесткими ограничениями платформы:

Для решения этой проблемы мы должны использовать специализированный системный COM-объект ADODB.Stream. Он позволяет работать с двоичными данными напрямую на уровне байтов, не вовлекая механизмы строкового преобразования 1С.

Пошаговое решение с использованием ADODB.Stream

Давайте разберем по шагам логику построения нашего запроса. Мы будем использовать два потока ADODB.Stream. Первый поток — основной (будет работать в бинарном режиме Type = 1), в нем мы соберем итоговое тело запроса. Второй поток — вспомогательный (текстовый, Type = 2), он понадобится нам для корректного перевода текстовых заголовков разметки в кодировку UTF-8 перед записью в основной поток.

Рассмотрим детальный и полностью рабочий программный код для 1С 7.7:


// Функция для генерации уникальной границы запроса
Функция ПолучитьУникальнуюГраницу()
    Возврат "----1C77Boundary" + ТекущееВремя();
КонецФункции

// Основная процедура отправки фото
Процедура ИмпортироватьФотографию(ИмяФайлаКартинки, ТокенАвторизации, ИДПользователя)
    
    // 1. Инициализируем объект HTTP-запроса
    WinHttp = СоздатьОбъект("WinHttp.WinHttpRequest.5.1");
    
    // Определяем URL и метод запроса
    АдресРесурса = "http://localhost:8080/api/rest.php/auth/users?action=update-user-photo";
    WinHttp.Open("POST", АдресРесурса, 0); // Синхронный режим
    
    // Устанавливаем необходимые заголовки
    Boundary = ПолучитьУникальнуюГраницу();
    WinHttp.SetRequestHeader("Authorization", "Bearer " + ТокенАвторизации);
    WinHttp.SetRequestHeader("Content-Type", "multipart/form-data; boundary=" + Boundary);
    
    // Символы перевода строки (CRLF)
    CRLF = Симв(13) + Симв(10);
    
    // 2. Формируем текстовые блоки multipart-разметки
    
    // Блок 1: Текстовый параметр UID пользователя
    БлокТекст_UID = "--" + Boundary + CRLF;
    БлокТекст_UID = БлокТекст_UID + "Content-Disposition: form-data; name=""uid""" + CRLF + CRLF;
    БлокТекст_UID = БлокТекст_UID + СокрЛП(ИДПользователя) + CRLF;
    
    // Блок 2: Заголовки для передаваемого файла изображения
    БлокТекст_Файл_Заголовок = "--" + Boundary + CRLF;
    БлокТекст_Файл_Заголовок = БлокТекст_Файл_Заголовок + "Content-Disposition: form-data; name=""file""; filename=""photo.jpg""" + CRLF;
    БлокТекст_Файл_Заголовок = БлокТекст_Файл_Заголовок + "Content-Type: image/jpeg" + CRLF + CRLF;
    
    // Блок 3: Закрывающий разделитель запроса
    БлокТекст_Финал = CRLF + "--" + Boundary + "--" + CRLF;
    
    // 3. Создаем основной двоичный поток для сборки всего тела
    ПотокОсновной = СоздатьОбъект("ADODB.Stream");
    ПотокОсновной.Type = 1; // 1 = Binary
    ПотокОсновной.Open();
    
    // Вспомогательный объект для перевода строк в UTF-8 без искажений
    ПотокТекст = СоздатьОбъект("ADODB.Stream");
    
    // Записываем Блок 1 (UID) в основной поток
    ПотокТекст.Type = 2; // 2 = Text
    ПотокТекст.CharSet = "utf-8";
    ПотокТекст.Open();
    ПотокТекст.WriteText(БлокТекст_UID);
    ПотокТекст.Position = 0;
    ПотокТекст.Type = 1; // Меняем тип на бинарный, чтобы прочитать байты
    ПотокОсновной.Write(ПотокТекст.Read());
    ПотокТекст.Close();
    
    // Записываем Блок 2 (Заголовок файла) в основной поток
    ПотокТекст.Type = 2;
    ПотокТекст.CharSet = "utf-8";
    ПотокТекст.Open();
    ПотокТекст.WriteText(БлокТекст_Файл_Заголовок);
    ПотокТекст.Position = 0;
    ПотокТекст.Type = 1;
    ПотокОсновной.Write(ПотокТекст.Read());
    ПотокТекст.Close();
    
    // Читаем бинарный файл картинки напрямую с диска и пишем его в основной поток
    ПотокФайла = СоздатьОбъект("ADODB.Stream");
    ПотокФайла.Type = 1; // Binary
    ПотокФайла.Open();
    ПотокФайла.LoadFromFile(ИмяФайлаКартинки);
    ПотокОсновной.Write(ПотокФайла.Read());
    ПотокФайла.Close();
    
    // Записываем Блок 3 (Финальная закрывающая граница) в основной поток
    ПотокТекст.Type = 2;
    ПотокТекст.CharSet = "utf-8";
    ПотокТекст.Open();
    ПотокТекст.WriteText(БлокТекст_Финал);
    ПотокТекст.Position = 0;
    ПотокТекст.Type = 1;
    ПотокОсновной.Write(ПотокТекст.Read());
    ПотокТекст.Close();
    
    // 4. Позиционируем итоговый поток в начало и отправляем запрос
    ПотокОсновной.Position = 0;
    
    Попытка
        WinHttp.Send(ПотокОсновной.Read()); // Передаем весь бинарный поток в метод Send
        ПотокОсновной.Close();
    Исключение
        Предупреждение("Ошибка при отправке HTTP-запроса: " + ОписаниеОшибки());
        ПотокОсновной.Close();
        Возврат;
    КонецПопытки;
    
    // 5. Обрабатываем ответ сервера
    СтатусЗапроса = WinHttp.Status;
    ТекстОтвета = WinHttp.ResponseText;
    
    Если СтатусЗапроса <> 200 Тогда
        Сообщить("Произошла ошибка при импорте фотографии!", "!");
        Сообщить("Код ответа сервера: " + СтатусЗапроса);
        Сообщить("Ответ сервера: " + ТекстОтвета);
    Иначе
        Сообщить("Фотография успешно импортирована для пользователя с UID: " + ИДПользователя);
    КонецЕсли;

КонецПроцедуры

Разбор принципа работы кода по шагам

Давайте подробно проанализируем, как работает эта схема и почему она гарантирует успешную отправку данных без сбоев:

  1. Установка заголовков: Мы явно указываем серверу тип контента multipart/form-data и передаем уникальное значение параметра boundary. Сервер будет использовать эту строку как разделитель блоков данных при получении пакета.
  2. Использование временного текстового потока: Каждый текстовый блок разметки мы сначала записываем в объект ADODB.Stream с установленным типом Type = 2 (Текст) и кодировкой CharSet = "utf-8". Это гарантирует, что кириллица или специальные символы корректно перекодируются в UTF-8.
  3. Трюк со сменой типа потока (Type Casting): Перед чтением текстового потока мы сбрасываем его позицию в начало (Position = 0) и переключаем свойство Type = 1 (Двоичный). Это позволяет нам прочитать «сырые» байты закодированной UTF-8 строки с помощью метода Read() и переписать их в наш главный результирующий поток без искажения кодовых страниц 1С 7.7.
  4. Прямое чтение файла изображения: Файл картинки считывается с диска бинарным потоком LoadFromFile и сразу же «байт-в-байт» копируется в конец результирующего потока. Никакого перекодирования файла не происходит, его целостность не нарушается.
  5. Единый вызов Send: Из главного потока считываются все байты целиком с помощью Read() и передаются в единственный вызов WinHttp.Send(). Сервер получает монолитный, корректно оформленный пакет данных и успешно обрабатывает его.

Применяя предложенную методику, вы сможете легко интегрировать конфигурации «1С:Предприятие 7.7» с любыми современными облачными сервисами, корпоративными порталами, личными кабинетами и внешними базами данных, требующими передачи двоичных файлов по протоколу HTTP. В качестве альтернативного варианта выполнения HTTP-запросов можно рассмотреть способ обмена данными через Msxml2.ServerXMLHTTP.6.0.

← На главную