При интеграции 1С с внешними веб-сервисами (API) одной из самых трудоемких задач является передача файлов и сложных структур данных через протокол HTTP. Формат multipart/form-data требует строгого соблюдения структуры тела запроса, где любая лишняя строка или неверный символ переноса могут привести к тому, что сервер не сможет «увидеть» передаваемые данные. В этой статье мы подробно разберем, как избежать типичных ошибок и правильно собрать запрос вручную.
Для начала проанализируем, из чего состоит стандартный запрос этого типа. Представим тело запроса как многослойный пирог, где слои разделены специальной строкой — «границей» (boundary). Каждая часть (part) запроса должна содержать свои собственные метаданные. Рассмотрим основные правила:
--boundary), а в самом конце всего тела запроса — дефисами и в начале, и в конце (--boundary--).Content-Disposition (с указанием имени поля и имени файла) и Content-Type (тип контента, например, image/jpeg).Одной из самых частых причин ошибок является использование неверных символов переноса строки. Протокол HTTP требует использования пары CRLF (Возврат каретки + Перевод строки). В коде 1С это соответствует комбинации Символы.ВК + Символы.ПС.
Если мы используем метод ЗаписатьСтроку() объекта ЗаписьДанных, платформа может подставить системное окончание строки, которое зависит от операционной системы сервера (Windows или Linux). Чтобы гарантировать корректность, разберем пример правильного формирования блока:
// Пример формирования заголовков внутри части
ЗаписьДанных.ЗаписатьСтроку("--" + Разделитель);
ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""file""; filename=""" + ИмяФайла + """");
ЗаписьДанных.ЗаписатьСтроку("Content-Type: image/jpeg");
ЗаписьДанных.ЗаписатьСтроку(""); // Та самая обязательная пустая строка
ЗаписьДанных.Записать(ДвоичныеДанныеФайла);
ЗаписьДанных.ЗаписатьСтроку(""); // Перенос перед следующим разделителем
Проанализируем ситуацию, с которой столкнулся автор темы. Одной из ключевых правок, которая привела к успеху, стало удаление заголовка Content-Length из основного запроса. Выясним причину, почему это происходит.
Когда мы вызываем метод ЗапросHTTP.УстановитьТелоИзДвоичныхДанных(ДвоичныеДанныеТело), платформа 1С самостоятельно вычисляет размер переданного массива байтов и автоматически добавляет корректный заголовок Content-Length. Если программист пытается рассчитать этот размер вручную, легко ошибиться на несколько байт (например, не учесть невидимые символы или маркер BOM в кодировке UTF-8). В результате сервер получает неверную информацию о длине потока, что приводит к обрыву соединения или игнорированию данных.
Важное правило: Доверяйте расчет длины контента платформе, не устанавливайте этот заголовок вручную в Заголовки.Вставить().
Рассмотрим полный цикл подготовки и отправки данных. Мы будем использовать ПотокВПамяти, чтобы эффективно собрать тело запроса из текстовых строк и бинарных данных файла.
Перед сборкой запроса необходимо получить двоичные данные файла. Если файл находится во временном хранилище, извлечем его:
ДанныеФайла = ПолучитьИзВременногоХранилища(АдресДвоичныхДанных);
Используем ЗаписьДанных для последовательной записи частей запроса. Обратите внимание на кодировку: лучше всего использовать КодировкаТекста.UTF8 без использования BOM (Byte Order Mark), так как лишние байты в начале потока могут сбить сервер с толку.
Разделитель = "MyUniqueBoundary" + СтрЗаменить(Строка(Новый УникальныйИдентификатор()), "-", "");
ПотокТело = Новый ПотокВПамяти();
// Создаем запись данных, явно указывая отсутствие BOM
ЗаписьДанных = Новый ЗаписьДанных(ПотокТело, КодировкаТекста.UTF8, ПорядокБайтов.BigEndian, "", Ложь);
// 1. Начальный разделитель
ЗаписьДанных.ЗаписатьСтроку("--" + Разделитель);
// 2. Заголовки части файла
ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""file""; filename=""image.jpg""");
ЗаписьДанных.ЗаписатьСтроку("Content-Type: image/jpeg");
ЗаписьДанных.ЗаписатьСтроку(""); // Пустая строка перед данными
// 3. Бинарные данные
ЗаписьДанных.Записать(ДанныеФайла);
// 4. Завершающий разделитель
ЗаписьДанных.ЗаписатьСтроку("");
ЗаписьДанных.ЗаписатьСтроку("--" + Разделитель + "--");
ЗаписьДанных.Закрыть();
РезультатТело = ПотокТело.ЗакрытьИПолучитьДвоичныеДанные();
Теперь соберем объект HTTPЗапрос. Помните, что заголовок Content-Type должен содержать не только тип multipart/form-data, но и само определение границы boundary.
Запрос = Новый HTTPЗапрос("/api/upload/");
Запрос.Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + Разделитель);
// Устанавливаем тело, Content-Length добавится автоматически
Запрос.УстановитьТелоИзДвоичныхДанных(РезультатТело);
Соединение = Новый HTTPСоединение("example.com");
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
Если Ответ.КодСостояния = 200 Тогда
Сообщить("Файл успешно отправлен!");
Иначе
Сообщить("Ошибка отправки: " + Ответ.ПолучитьТелоКакСтроку());
КонецЕсли;
Проанализируем еще несколько важных аспектов, которые помогут сделать ваш код более надежным:
name="file" в заголовке Content-Disposition совпадает с тем, что ожидает сервер. Часто разработчики путают name (имя переменной в API) и filename (имя самого файла). Если name будет указан неверно, сервер вернет ошибку о пустом файле.КодироватьСтроку(ИмяФайла, СпособКодированияСтроки.URLКодирование) или передавать имя в латинице.Таким образом, успех при работе с multipart/form-data в 1С зависит от ювелирной точности в расстановке разделителей и пустых строк, а также от правильного использования объектов ПотокВПамяти и ЗаписьДанных. Отказ от ручного управления Content-Length является ключом к избеганию большинства ошибок при передаче бинарного контента.