Как правильно отправить POST-запрос с параметрами и файлами (multipart/form-data и urlencoded) в 1С?

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

При интеграции систем 1С с внешними веб-сервисами, сайтами, API или CRM-системами разработчики часто сталкиваются с необходимостью отправки POST-запросов. Существует два основных способа передачи параметров в теле такого запроса: стандартное URL-кодирование (application/x-www-form-urlencoded) и отправка составных данных (multipart/form-data).

В этой статье мы подробно разберем оба подхода, проанализируем типичные ошибки разработчиков (такие как проблемы со спецсимволами, некорректные заголовки и маркеры кодировок) и научимся формировать правильные запросы как классическими методами, так и с использованием современных механизмов платформы 1С:Предприятие 8.3.

Способ 1. Использование application/x-www-form-urlencoded и исправление частых ошибок

Этот способ чаще всего применяется для передачи простых текстовых параметров, таких как логин, пароль, номер телефона или поисковый запрос. Данные кодируются в виде пар «ключ-значение», разделенных символом амперсанда (&).

Рассмотрим типичную ситуацию. Разработчик пытается отправить логин и пароль на веб-сервер. В процессе тестирования выясняется, что для пользователей с простыми паролями (состоящими только из латинских букв и цифр) запрос отрабатывает успешно. Однако, если в пароле или логине появляется спецсимвол, например плюс (+), решетка (#) или пробел, сервер возвращает ошибку авторизации или некорректную HTML-страницу.

Выясним причину этой проблемы. Символ плюс (+) в формате URL-кодирования интерпретируется сервером как пробел. В результате пароль на стороне сервера искажается. Чтобы избежать искажения данных, все передаваемые значения обязательно нужно пропускать через специальное кодирование.

Разберем по шагам, как исправить эту ошибку с помощью встроенного метода КодироватьСтроку. Также важно обратить внимание на структуру тела запроса: перед самым первым параметром не должно быть лишнего символа амперсанда &. Строка запроса должна начинаться сразу с имени первого ключа.

Посмотрим на пример правильного формирования такого запроса:


// Подготовка параметров соединения
Сервер = "api.mysite.ru";
Пользователь = "admin";
Пароль = "my+secure#password"; // Содержит спецсимволы, требующие кодирования

HTTPСоединение = Новый HTTPСоединение(Сервер);

// Параметры запроса (часть URL-адреса)
ПараметрыЗапроса = "api/apicrm/phonenumber/79991112233";

// Кодируем параметры тела запроса для безопасной передачи по сети
КодированныйЛогин = КодироватьСтроку(Пользователь, СпособКодированияСтроки.КодировкаURL);
КодированныйПароль = КодироватьСтроку(Пароль, СпособКодированияСтроки.КодировкаURL);

// Строим тело запроса. Важно: никакого "&" в самом начале строки!
ТелоЗапроса = "login=" + КодированныйЛогин + "&password=" + КодированныйПароль;

Заголовки = Новый Соответствие;
Заголовки.Вставить("Content-Type", "application/x-www-form-urlencoded");

HTTPЗапрос = Новый HTTPЗапрос(ПараметрыЗапроса, Заголовки);
HTTPЗапрос.УстановитьТелоИзСтроки(ТелоЗапроса, КодировкаТекста.UTF8);

ИмяВыходногоФайла = ПолучитьИмяВременногоФайла("xml");
HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос, ИмяВыходногоФайла);

Способ 2. Переход на multipart/form-data и разбор критических ошибок

Когда требуется передать файлы или если внешнее API жестко требует использования multipart-запросов, обычным URL-кодированием не обойтись. Разметка multipart/form-data делит тело запроса на отдельные части (блоки), каждая из которых отделяется уникальной строкой-разделителем — boundary.

Проанализируем типичные критические ошибки, которые часто допускают программисты при ручной сборке multipart-запросов (например, при записи текста во временный файл):

  1. Символ разделения параметров в заголовке Content-Type. По спецификации RFC разделителем параметров в заголовках HTTP должна быть строго точка с запятой (;). Ошибка в виде использования запятой (например, multipart/form-data, boundary=...) приводит к тому, что сервер не может определить границу блоков и считает запрос пустым.
  2. Опечатка в названии заголовка длины контента. Вместо стандартного Content-Length разработчики часто пишут с опечаткой — Content-Lenght. Сервер просто проигнорирует такой заголовок, что может вызвать зависание веб-сервера при чтении потока данных.
  3. Проблема BOM (Byte Order Mark) в UTF-8. При записи текстового файла через ЗаписьТекста платформа 1С по умолчанию добавляет в начало файла невидимый маркер кодировки BOM (три байта: EF BB BF). Веб-сервер, начиная парсинг тела запроса, видит эти байты непосредственно перед первой границей --boundary и завершает обработку с ошибкой разбора.
  4. Некорректный перенос строк (CRLF). Стандарты HTTP строго требуют разделять строки внутри тела multipart-запроса парой символов возврата каретки и перевода строки — \r\n (CRLF). Стандартный метод ЗаписатьСтроку() ориентируется на системные настройки операционной системы. Если код выполняется на сервере 1С под управлением Linux, строки будут разделены только символом \n (LF), что гарантированно вызовет сбой разбора запроса веб-сервером.

Современный и надежный способ формирования multipart-запроса в памяти

Начиная с версии платформы 1С:Предприятие 8.3.9, мы можем полностью отказаться от записи временных файлов на жесткий диск. Гораздо быстрее и надежнее собрать весь запрос непосредственно в оперативной памяти с помощью объектов ПотокВПамяти и ЗаписьДанных. Этот способ гарантирует отсутствие BOM-маркеров и позволяет жестко контролировать символы переноса строк независимо от операционной системы (Windows или Linux).

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


// Инициализируем уникальную границу блоков запроса
Boundary = СтрЗаменить(Строка(Новый УникальныйИдентификатор()), "-", "");

// Задаем строгий разделитель строк CRLF соответствии со спецификацией HTTP (\r\n)
РазделительСтрок = Символы.ВК + Символы.ПС; 

ПотокТела = Новый ПотокВПамяти();

// Важно: шестой параметр "Ложь" отключает запись BOM-маркера в поток UTF-8
ЗаписьДанных = Новый ЗаписьДанных(ПотокТела, КодировкаТекста.UTF8, , , , Ложь);

// Записываем блок параметра "login"
ЗаписьДанных.ЗаписатьСтроку("--" + Boundary + РазделительСтрок);
ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""login""" + РазделительСтрок);
ЗаписьДанных.ЗаписатьСтроку(РазделительСтрок); // Пустая строка перед значением
ЗаписьДанных.ЗаписатьСтроку(Пользователь + РазделительСтрок);

// Записываем блок параметра "password"
ЗаписьДанных.ЗаписатьСтроку("--" + Boundary + РазделительСтрок);
ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""password""" + РазделительСтрок);
ЗаписьДанных.ЗаписатьСтроку(РазделительСтрок);
ЗаписьДанных.ЗаписатьСтроку(Пароль + РазделительСтрок);

// Записываем финальную закрывающую границу запроса
ЗаписьДанных.ЗаписатьСтроку("--" + Boundary + "--" + РазделительСтрок);
ЗаписьДанных.Закрыть();

// Получаем готовые двоичные данные из потока памяти
ДвоичныеДанныеТела = ПотокТела.ЗакрытьИПолучитьДвоичныеДанные();

// Формируем HTTP-запрос с корректными заголовками
Заголовки = Новый Соответствие();
Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + Boundary); // Используем ";" вместо ","
Заголовки.Вставить("Content-Length", Строка(ДвоичныеДанныеТела.Размер())); // Корректное написание Length

HTTPЗапрос = Новый HTTPЗапрос(ПараметрыЗапроса, Заголовки);
HTTPЗапрос.УстановитьТелоИзДвоичныхДанных(ДвоичныеДанныеТела);

// Отправляем запрос на сервер
HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос, ИмяВыходногоФайла);

Передача файлов в multipart/form-data запросе

Посмотрим на практический пример, когда помимо текстовых полей нам нужно прикрепить к запросу бинарный файл (например, изображение или документ PDF). В этом случае структура блока файла будет немного отличаться: нам потребуется передать имя файла в параметре filename и указать заголовок Content-Type для конкретного файла.

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


ПутьКФайлуОтправки = "C:\temp\report.pdf";
ФайлНаДиске = Новый Файл(ПутьКФайлуОтправки);

Если ФайлНаДиске.Существует() Тогда
    ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ПутьКФайлуОтправки);
    
    Boundary = СтрЗаменить(Строка(Новый УникальныйИдентификатор()), "-", "");
    РазделительСтрок = Символы.ВК + Символы.ПС;
    
    ПотокТела = Новый ПотокВПамяти();
    ЗаписьДанных = Новый ЗаписьДанных(ПотокТела, КодировкаТекста.UTF8, , , , Ложь);
    
    // Текстовый параметр "comment"
    ЗаписьДанных.ЗаписатьСтроку("--" + Boundary + РазделительСтрок);
    ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""comment""" + РазделительСтрок);
    ЗаписьДанных.ЗаписатьСтроку(РазделительСтрок);
    ЗаписьДанных.ЗаписатьСтроку("Отчет за прошлый месяц" + РазделительСтрок);
    
    // Блок передачи двоичного файла
    ЗаписьДанных.ЗаписатьСтроку("--" + Boundary + РазделительСтрок);
    ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""document""; filename=""report.pdf""" + РазделительСтрок);
    ЗаписьДанных.ЗаписатьСтроку("Content-Type: application/pdf" + РазделительСтрок);
    ЗаписьДанных.ЗаписатьСтроку(РазделительСтрок);
    
    // Записываем бинарные данные файла непосредственно в поток данных
    ЗаписьДанных.Записать(ДвоичныеДанныеФайла);
    ЗаписьДанных.ЗаписатьСтроку(РазделительСтрок); // Перенос строки после двоичных данных
    
    // Закрывающая граница всего запроса
    ЗаписьДанных.ЗаписатьСтроку("--" + Boundary + "--" + РазделительСтрок);
    ЗаписьДанных.Закрыть();
    
    ДвоичныеДанныеТела = ПотокТела.ЗакрытьИПолучитьДвоичныеДанные();
    
    Заголовки = Новый Соответствие();
    Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + Boundary);
    Заголовки.Вставить("Content-Length", Строка(ДвоичныеДанныеТела.Размер()));
    
    HTTPЗапрос = Новый HTTPЗапрос(ПараметрыЗапроса, Заголовки);
    HTTPЗапрос.УстановитьТелоИзДвоичныхДанных(ДвоичныеДанныеТела);
    
    HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос, ИмяВыходногоФайла);
КонецЕсли;

Использование БСП для отправки запросов

Если вы разрабатываете интеграцию в типовой конфигурации 1С, построенной на основе БСП (Библиотеки Стандартных Подсистем), рекомендуем использовать штатные методы для интеграции. В модулях БСП часто присутствуют готовые вспомогательные функции для сборки сложных HTTP-запросов, включая автоматическое формирование уникальных границ (boundary) и кодирование параметров, что значительно сокращает время на написание и тестирование кода интеграции.

← На главную