При интеграции устаревших систем на платформе 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.
Давайте проанализируем исходный код автора темы. Пытаясь отправить идентификатор пользователя 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-запроса нам придется собирать вручную.
Чтобы сервер смог корректно распарсить наш запрос, мы должны сформировать его тело в строгом соответствии со стандартом 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--
Рассмотрим ключевые особенности этой структуры:
----BoundaryString), которая не должна встречаться внутри передаваемых данных. Она отделяет один параметр от другого. Граница обязательно указывается в заголовке запроса Content-Type.CRLF. В языке 1С 7.7 это реализуется как Симв(13) + Симв(10).------BoundaryString), затем идут заголовки поля (например, Content-Disposition), пустая строка (двойной CRLF) и само значение параметра.------BoundaryString--.Казалось бы, мы можем просто собрать всё тело запроса как обычную строку в 1С 7.7, склеив текст разметки с прочитанным файлом картинки, и передать эту строку в метод Send(). Однако здесь мы сталкиваемся с жесткими ограничениями платформы:
null-byte, он же Симв(0)) будут восприняты как признак окончания строки, из-за чего строка просто обрежется.Для решения этой проблемы мы должны использовать специализированный системный COM-объект ADODB.Stream. Он позволяет работать с двоичными данными напрямую на уровне байтов, не вовлекая механизмы строкового преобразования 1С.
Давайте разберем по шагам логику построения нашего запроса. Мы будем использовать два потока 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: " + ИДПользователя);
КонецЕсли;
КонецПроцедуры
Давайте подробно проанализируем, как работает эта схема и почему она гарантирует успешную отправку данных без сбоев:
multipart/form-data и передаем уникальное значение параметра boundary. Сервер будет использовать эту строку как разделитель блоков данных при получении пакета.ADODB.Stream с установленным типом Type = 2 (Текст) и кодировкой CharSet = "utf-8". Это гарантирует, что кириллица или специальные символы корректно перекодируются в UTF-8.Position = 0) и переключаем свойство Type = 1 (Двоичный). Это позволяет нам прочитать «сырые» байты закодированной UTF-8 строки с помощью метода Read() и переписать их в наш главный результирующий поток без искажения кодовых страниц 1С 7.7.LoadFromFile и сразу же «байт-в-байт» копируется в конец результирующего потока. Никакого перекодирования файла не происходит, его целостность не нарушается.Read() и передаются в единственный вызов WinHttp.Send(). Сервер получает монолитный, корректно оформленный пакет данных и успешно обрабатывает его.Применяя предложенную методику, вы сможете легко интегрировать конфигурации «1С:Предприятие 7.7» с любыми современными облачными сервисами, корпоративными порталами, личными кабинетами и внешними базами данных, требующими передачи двоичных файлов по протоколу HTTP. В качестве альтернативного варианта выполнения HTTP-запросов можно рассмотреть способ обмена данными через Msxml2.ServerXMLHTTP.6.0.