Почему при отправке файлов DOC или XLS через HTTP POST возникает ошибка 500 и как её исправить в 1С?

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

При интеграции 1С с внешними веб-сервисами и сайтами разработчики часто сталкиваются с задачей передачи файлов через HTTP-запрос методом POST в формате multipart/form-data. Нередко возникает ситуация, когда файлы одного типа (например, PDF) загружаются на веб-сервер без ошибок, а при передаче документов Word (.doc, .docx) или таблиц Excel (.xls, .xlsx) сервер возвращает ответ 500 Internal Server Error.

Разберем причины такого поведения веб-серверов и рассмотрим по шагам, как правильно формировать тело составного HTTP-запроса в среде 1С:Предприятие.

Анализ проблемы: почему возникает ошибка 500

Проанализируем типовую ситуацию. Исходный код формирования составного запроса через временные файлы выглядит следующим образом:


ИмяФайлаОтправки = ПолучитьИмяВременногоФайла("txt");
Boundary = СтрЗаменить(Строка(Новый УникальныйИдентификатор()), "-", "");    

МассивФайловДляОбъединения = Новый Массив; 

ИмяФайлаОтправкиНачало = ПолучитьИмяВременногоФайла("txt"); 
ФайлОтправкиНачало = Новый ЗаписьТекста(ИмяФайлаОтправкиНачало, КодировкаТекста.ANSI, Символы.ПС, Ложь); 
ФайлОтправкиНачало.ЗаписатьСтроку(""); 
ФайлОтправкиНачало.ЗаписатьСтроку("------" + Boundary); 
ФайлОтправкиНачало.ЗаписатьСтроку("Content-Disposition: form-data; name=""file""; filename=""" + ИмяФайла + """"); 
ФайлОтправкиНачало.ЗаписатьСтроку(""); 
ФайлОтправкиНачало.Закрыть(); 

МассивФайловДляОбъединения.Добавить(ИмяФайлаОтправкиНачало); 

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

ИмяФайлаОтправкиКонец = ПолучитьИмяВременногоФайла("txt"); 
ФайлОтправкиКонец = Новый ЗаписьТекста(ИмяФайлаОтправкиКонец, КодировкаТекста.ANSI, Символы.ПС, Ложь); 
ФайлОтправкиКонец.ЗаписатьСтроку(""); 
ФайлОтправкиКонец.ЗаписатьСтроку("------" + Boundary); 
ФайлОтправкиКонец.ЗаписатьСтроку("Content-Disposition: form-data; name=""target""" + Символы.ПС); 
ФайлОтправкиКонец.ЗаписатьСтроку(СтруктураТипаДанных.Назначение); 
ФайлОтправкиКонец.ЗаписатьСтроку("------" + Boundary + "--"); 
ФайлОтправкиКонец.Закрыть(); 

МассивФайловДляОбъединения.Добавить(ИмяФайлаОтправкиКонец); 
ОбъединитьФайлы(МассивФайловДляОбъединения, ИмяФайлаОтправки);

Выясним главные причины сбоя парсера на стороне веб-сервера:

  1. Кириллические символы в параметре filename: При записи текста с параметром КодировкаТекста.ANSI русскоязычное имя файла попадает в заголовок в кодировке Windows-1251. Веб-серверы (Nginx, Apache) и серверные обработчики при разборе заголовков multipart/form-data ожидают кодировку UTF-8 либо исключительно символы US-ASCII. Встретив некорректные байты, парсер аварийно завершает работу с кодом 500.
  2. Некорректные символы перевода строк: По стандарту HTTP все строки заголовков и разделители частей должны отделяться последовательностью CRLF (\r\n, в 1С это Символы.ВК + Символы.ПС). Использование только Символы.ПС (LF) нарушает спецификацию RFC.
  3. Неверное количество дефисов в границах (Boundary): В заголовке запроса Content-Type: multipart/form-data; boundary=... объявляется строка границы. В теле запроса перед этой строкой должно стоять ровно два дефиса --, а в финальной границе — два дефиса до и два дефиса после: --Boundary--. Лишние дефисы в теле запроса приводят к тому, что парсер не видит разделитель.
  4. Отсутствие или некорректный Content-Type для бинарной части: Если заголовок с типом MIME не передан, веб-сервер может попытаться интерпретировать бинарный офисный документ как текстовый поток, что вызывает ошибку валидации.

Способ 1. Исправление имени файла и кодировки

Самое простое и быстрое решение — передавать в параметре filename только латинские символы без пробелов и спецсимволов. Если необходимо сохранить оригинальное русскоязычное имя, следует использовать стандарт RFC 5987 / RFC 6266 с URL-кодированием с помощью функции КодироватьСтроку.

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


// Вариант 1: Приведение имени к латинице (транслитерация или безопасное имя)
ИмяФайлаБезопасное = "document.doc";
СтрокаЗаголовка = "Content-Disposition: form-data; name=""file""; filename=""" + ИмяФайлаБезопасное + """";

// Вариант 2: Передача оригинального имени по стандарту RFC 5987
ИмяФайлаURL = КодироватьСтроку(ИмяФайла, СпособКодированияСтроки.КодировкаURL);
СтрокаЗаголовка = "Content-Disposition: form-data; name=""file""; filename=""" + ИмяФайлаБезопасное + """; filename*=UTF-8''" + ИмяФайлаURL;

Способ 2. Корректное указание MIME-типов для офисных файлов

Обязательно передадим серверу точный заголовок Content-Type для передаваемой части файла. Рассмотрим соответствие расширений и MIME-типов:

  1. Для файлов Word формата DOC (.doc): application/msword
  2. Для файлов Word формата DOCX (.docx): application/vnd.openxmlformats-officedocument.wordprocessingml.document
  3. Для таблиц Excel формата XLS (.xls): application/vnd.ms-excel
  4. Для таблиц Excel формата XLSX (.xlsx): application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
  5. Универсальный тип для произвольных бинарных данных: application/octet-stream

Способ 3. Формирование тела запроса в памяти через ЗаписьДанных

Вместо создания множества файлов на диске и вызова метода ОбъединитьФайлы, в современных версиях платформы 1С:Предприятие рекомендуется собирать тело запроса целиком в оперативной памяти с помощью объектов ПотокВПамяти, ЗаписьДанных и ЧтениеДанных.

Разберем по шагам готовый пример функции для сборки запроса:


Функция СформироватьТелоЗапроса(ИмяФайлаДанных, ИмяФайлаДляСервера, ЗначениеTarget, Boundary)
    
    CRLF = Символы.ВК + Символы.ПС;
    
    Поток = Новый ПотокВПамяти;
    Запись = Новый ЗаписьДанных(Поток, КодировкаТекста.UTF8);
    
    // 1. Формируем часть с файлом
    Запись.ЗаписатьСтроку("--" + Boundary + CRLF);
    
    // Формируем заголовок с транслитерированным/латинским именем или стандартом UTF-8
    ИмяФайлаURL = КодироватьСтроку(ИмяФайлаДляСервера, СпособКодированияСтроки.КодировкаURL);
    Запись.ЗаписатьСтроку("Content-Disposition: form-data; name=""file""; filename=""file.bin""; filename*=UTF-8''" + ИмяФайлаURL + CRLF);
    Запись.ЗаписатьСтроку("Content-Type: application/octet-stream" + CRLF);
    Запись.ЗаписатьСтроку(CRLF);
    Запись.СброситьБуферы();
    
    // 2. Записываем бинарное содержимое файла в поток
    ФайлЧтение = Новый ЧтениеДанных(Новый ФайловыйПоток(ИмяФайлаДанных, РежимОткрытияФайла.Открыть));
    Запись.КопироватьИз(ФайлЧтение);
    ФайлЧтение.Закрыть();
    
    // 3. Формируем текстовые поля формы
    Запись.ЗаписатьСтроку(CRLF + "--" + Boundary + CRLF);
    Запись.ЗаписатьСтроку("Content-Disposition: form-data; name=""target""" + CRLF);
    Запись.ЗаписатьСтроку(CRLF);
    Запись.ЗаписатьСтроку(ЗначениеTarget);
    
    // 4. Финальная закрывающая граница
    Запись.ЗаписатьСтроку(CRLF + "--" + Boundary + "--" + CRLF);
    Запись.СброситьБуферы();
    
    // Получаем двоичные данные собранного запроса
    Результат = Поток.ЗакрытьИПолучитьДвоичныеДанные();
    Возврат Результат;
    
КонецФункции

Рассмотрим отправку сформированных данных через HTTPСоединение:


Boundary = "----1CBoundary" + СтрЗаменить(Строка(Новый УникальныйИдентификатор()), "-", "");
ТелоЗапроса = СформироватьТелоЗапроса("C:\temp\report.doc", "Отчет.doc", "UploadTarget", Boundary);

Заголовки = Новый Соответствие;
Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + Boundary);

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

Соединение = Новый HTTPСоединение("example.com", 443, , , , 30, Новый ЗащищенноеСоединениеOpenSSL());
HTTPОтвет = Соединение.ОтправитьДляОбработки(HTTPЗапрос);

Использование правильных переводов строк CRLF, корректного формата границ --Boundary и безопасных латинских имен файлов с URL-кодированием позволяет полностью устранить ошибку 500 при выгрузке любых офисных документов из 1С.

← На главную