В современной разработке на платформе 1С:Предприятие 8 часто возникает необходимость интеграции с внешними веб-сервисами через HTTP API. Одной из самых трудоемких задач является корректное формирование запроса в формате multipart/form-data. Часто разработчики сталкиваются с ситуацией, когда в сторонних инструментах, таких как Postman, запрос проходит успешно, а 1С возвращает неочевидные ошибки вроде "Not Allowed" или "403 Forbidden". В этой статье мы детально разберем, как правильно сконструировать такой запрос, проанализируем типичные ошибки в синтаксисе заголовков и изучим современные подходы к работе с бинарными данными.
Рассмотрим ситуацию, когда при идентичных, на первый взгляд, параметрах сервер отказывает в доступе именно запросу из 1С. Выясним причину на конкретном примере. Проанализируем фрагмент кода, где формируется заголовок запроса:
Заголовки = Новый Соответствие;
Заголовки.Вставить("Storage-Token", "5d******99dff");
Заголовки.Вставить("Content-Type","multipart/form-data; boundary =" + Разделитель);
Проблема здесь кроется в строгом соблюдении стандартов протокола HTTP. В строке "multipart/form-data; boundary =" перед знаком равенства стоит лишний пробел. Для многих веб-серверов (например, на базе Nginx или специализированных API-шлюзов) это делает параметр boundary нечитаемым. Сервер не может найти разделитель в теле запроса, считает структуру данных нарушенной и возвращает ошибку доступа или метода, так как не может распознать части multipart. Посмотрим на правильный вариант написания:
Заголовки.Вставить("Content-Type","multipart/form-data; boundary=" + Разделитель);
Важно: Пробел после точки с запятой допустим, но вокруг знака "=" в параметре boundary его быть не должно. Это критический нюанс, который часто упускается при ручном формировании строк.
Разберем классический метод сборки тела запроса с использованием объекта ТекстовыйДокумент. Этот метод подходит для текстовых файлов (XML, JSON), но требует внимательности к каждому символу. Проанализируем последовательность действий:
Посмотрим на пример реализации:
Разделитель = СтрЗаменить(Новый УникальныйИдентификатор(), "-", "");
ТелоЗапроса = Новый ТекстовыйДокумент();
// Важно использовать именно ВК + ПС для соответствия стандарту HTTP
ТелоЗапроса.РазделительСтрок = Символы.ВК + Символы.ПС;
// Первая часть - файл
ТелоЗапроса.ДобавитьСтроку("--" + Разделитель);
ТелоЗапроса.ДобавитьСтроку("Content-Disposition: form-data; name=""file""; filename=""stocks.xml""");
ТелоЗапроса.ДобавитьСтроку("Content-Type: application/xml");
ТелоЗапроса.ДобавитьСтроку(""); // Пустая строка перед данными
ТелоЗапроса.ДобавитьСтроку(СтрXML);
// Вторая часть - дополнительное поле (например, путь)
ТелоЗапроса.ДобавитьСтроку("--" + Разделитель);
ТелоЗапроса.ДобавитьСтроку("Content-Disposition: form-data; name=""path""");
ТелоЗапроса.ДобавитьСтроку(""); // Пустая строка перед значением
ТелоЗапроса.ДобавитьСтроку("/");
// Финализация тела
ТелоЗапроса.ДобавитьСтроку("--" + Разделитель + "--");
ТелоЗапросаСтрока = ТелоЗапроса.ПолучитьТекст();
Обратим внимание на свойство РазделительСтрок. Стандарт RFC требует использования последовательности CRLF. Если ваш сервер работает на Linux, а 1С настроена на стандартные системные разделители, это может привести к ошибке обработки. Всегда явно указывайте Символы.ВК + Символы.ПС.
Метод формирования через ТекстовыйДокумент имеет существенный недостаток: он плохо подходит для передачи бинарных файлов (картинок, архивов) и может искажать кодировку. Начиная с версии платформы 8.3.10, мы рекомендуем использовать ПотокВПамяти и объект КопироватьДанные. Это позволит объединить текстовые заголовки и сырые двоичные данные без риска повреждения файла.
Рассмотрим, как это реализовать:
// Создаем поток для сборки тела
ПотокРезультат = Новый ПотокВПамяти();
ЗаписьДанных = Новый ЗаписьДанных(ПотокРезультат, КодировкаТекста.UTF8, , Ложь); // Ложь - без BOM
// Записываем начало
ЗаписьДанных.ЗаписатьСтроку("--" + Разделитель);
ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""file""; filename=""data.xml""");
ЗаписьДанных.ЗаписатьСтроку("Content-Type: application/xml");
ЗаписьДанных.ЗаписатьСтроку(""); // Разделитель между заголовком и данными
// Записываем само тело файла (если это ДвоичныеДанные)
ЗаписьДанных.Закрыть(); // Скидываем буфер текста в поток
ПотокФайла = ДвоичныеДанныеФайла.ОткрытьПотокДляЧтения();
КопироватьДанные(ПотокФайла, ПотокРезультат);
ПотокФайла.Закрыть();
// Снова открываем запись для финала
ЗаписьДанных = Новый ЗаписьДанных(ПотокРезультат, КодировкаТекста.UTF8, , Ложь);
ЗаписьДанных.ЗаписатьСтроку(""); // Завершающий перевод строки после данных файла
ЗаписьДанных.ЗаписатьСтроку("--" + Разделитель + "--");
ЗаписьДанных.Закрыть();
// Получаем итоговые данные для запроса
ДвоичныеДанныеТела = ПотокРезультат.ЗакрытьИПолучитьДвоичныеДанные();
HTTPЗапрос.УстановитьТелоИзДвоичныхДанных(ДвоичныеДанныеТела);
Этот метод гарантирует, что 1С не добавит в начало запроса невидимую метку BOM (Byte Order Mark), которая часто становится причиной отказа серверов принимать запрос. Использование КопироватьДанные напрямую в поток — это наиболее производительный и безопасный способ передачи файлов любого размера.
Если запрос все равно не проходит, проанализируем ситуацию с помощью "снифферов" трафика (например, Fiddler или Wireshark). Сравним "сырой" (raw) текст запроса из Postman и из 1С. Проверим следующие моменты:
УстановитьТело.... Убедимся, что мы не переопределяем его вручную неправильным значением./~/upload, то и в объекте HTTPЗапрос параметр АдресРесурса должен начинаться с символа /. Это особенно актуально при тестировании запросов к ВетИС.Storage-Token или Content-Type, например, при подписи RSA с хешированием.multipart и её телом обязательно должна быть одна пустая строка. Ошибка в количестве этих строк — частая причина 400-х ошибок.В завершение подчеркнем: при работе с HTTP-запросами в 1С "дьявол кроется в деталях". Лишний пробел, отсутствие CRLF или наличие метки BOM могут полностью блокировать интеграцию. Используя современные объекты платформы и внимательно проверяя синтаксис заголовков, мы сможем построить надежный механизм обмена данными с любой внешней системой.