При интеграции систем 1С со сторонними веб-сервисами разработчики часто используют HTTP-запросы. В процессе отладки таких механизмов можно столкнуться с крайне неприятной ошибкой: «Ошибка работы с Интернет: Failure when receiving data from the peer». На уровне сетевых утилит и снифферов трафика (например, Fiddler или Wireshark) при этом фиксируется странное поведение — запрос даже не пытается покинуть локальный компьютер и не отправляется в сеть.
В этой статье мы подробно проанализируем причины возникновения этой ошибки, разберем ограничения сетевых библиотек и веб-серверов, а также рассмотрим правильные архитектурные шаблоны для передачи файлов и больших объемов данных по протоколу HTTP в 1С.
Для выполнения HTTP-запросов платформа 1С использует встроенную кроссплатформенную библиотеку libcurl. Ошибка «Failure when receiving data from the peer» (код ошибки 56 в спецификации libcurl) обычно означает, что сетевой сеанс был аварийно прерван до завершения транзакции.
Когда разработчик пытается упаковать тяжелые данные (например, сериализованный в Base64 файл размером 143 КБ или более) в HTTP-заголовок (Header) вместо тела запроса (Body), срабатывают жесткие ограничения безопасности:
libcurl жестко задан максимальный размер одного HTTP-заголовка — константа CURL_MAX_HTTP_HEADER, которая составляет ровно 100 КБ (102 400 байт). Если длина строки заголовка превышает этот лимит, библиотека прерывает выполнение операции на клиенте еще до фактической отправки сетевых пакетов в сокет. Именно поэтому сетевой сниффер показывает, что запрос не покидает компьютер.Nginx лимит буфера заголовка по умолчанию составляет от 4 до 8 КБ (параметр large_client_header_buffers), в Apache — 8 КБ (LimitRequestFieldSize), а в IIS — около 8–16 КБ. Сервер вернул бы ошибку HTTP 400 Bad Request или вовсе оборвал соединение.Таким образом, передавать бинарные данные, файлы или тяжелые структуры JSON внутри HTTP-заголовков категорически запрещено спецификацией протокола HTTP и техническими возможностями сетевого ПО.
Проанализируем структуру HTTP-запроса, чтобы понять, в чем заключается принципиальная разница между элементами:
client_max_body_size в Nginx).Рассмотрим, как исправить неверный код. В исходном (ошибочном) варианте данные помещались в соответствие заголовков:
// НЕПРАВИЛЬНО: Попытка передать файл в заголовках запроса
Заголовки = Новый Соответствие();
Заголовки.Вставить("json", ЗаписьДжсон.Закрыть()); // Сбой, если размер строки > 100 КБ
Для исправления ситуации мы должны переписать логику так, чтобы метаданные (тип контента) уходили в заголовках, а сериализованная JSON-строка с файлом помещалась непосредственно в тело POST-запроса с помощью метода УстановитьТелоИзСтроки().
Разберем правильную реализацию по шагам:
// Шаг 1: Формируем структуру с полезными данными
СтруктураНаСервер = Новый Структура;
СтруктураНаСервер.Вставить("Data", Base64Строка(Выборка.ДвоичныеДанныеФайла.Получить()));
СтруктураНаСервер.Вставить("Extension", Выборка.Расширение);
// Шаг 2: Сериализуем структуру в JSON-строку
ЗаписьДжсон = Новый ЗаписьJSON;
ЗаписьДжсон.УстановитьСтроку();
ЗаписатьJSON(ЗаписьДжсон, СтруктураНаСервер);
СтрокаJSON = ЗаписьДжсон.Закрыть();
// Шаг 3: Создаем HTTP-запрос
Запрос = Новый HTTPЗапрос(АдресРесурса);
// Шаг 4: Указываем мета-заголовки (тип содержимого и кодировку)
// Это небольшие служебные данные, они легко пройдут любые ограничения
Запрос.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
// Шаг 5: Помещаем тяжелые данные (нашу JSON-строку) в тело запроса
Запрос.УстановитьТелоИзСтроки(СтрокаJSON, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
// Шаг 6: Отправляем запрос на сервер в рамках установленного HTTPСоединения
Попытка
Результат = HTTPСоединение.ОтправитьДляОбработки(Запрос);
// Обрабатываем ответ сервера...
Исключение
ЗаписьЖурналаРегистрации("Интеграция.ОтправкаФайла",
УровеньЖурналаРегистрации.Ошибка, , ,
"Ошибка при отправке HTTP-запроса: " + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
Если принимающей стороной выступает созданный вами HTTP-сервис на платформе 1С, его код также требует корректировки. Вместо неверного извлечения данных из коллекции заголовков:
// НЕПРАВИЛЬНО: Попытка прочитать данные из заголовка
ДанныеJSON = Запрос.Заголовки.Получить("json");
Мы должны считывать данные непосредственно из тела запроса с помощью встроенного метода ПолучитьТелоКакСтроку(). Рассмотрим пример правильной обработки в методе HTTP-сервиса:
Функция ФайлыPOST(Запрос)
Ответ = Новый HTTPСервисОтвет(200);
Попытка
// Извлекаем JSON-строку из тела входящего запроса
СтрокаJSON = Запрос.ПолучитьТелоКакСтроку("UTF-8");
// Десериализуем JSON в структуру 1С
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтрокаJSON);
ДанныеФайла = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
// Извлекаем двоичные данные файла из Base64 и расширение
ДвоичныеДанные = ПолучитьДвоичныеДанныеИзBase64Строки(ДанныеФайла.Data);
РасширениеФайла = ДанныеФайла.Extension;
// Далее выполняем логику по обработке или сохранению файла в системе
// ...
Ответ.УстановитьТелоИзСтроки("Файл успешно обработан", КодировкаТекста.UTF8);
Исключение
Ответ.КодСостояния = 500;
Ответ.УстановитьТелоИзСтроки("Внутренняя ошибка сервера: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()), КодировкаТекста.UTF8);
КонецПопытки;
Возврат Ответ;
КонецФункции
Рассмотрим важный нюанс. Преобразование двоичных данных в строку формата Base64 увеличивает итоговый размер передаваемых данных примерно на 33%. Если вы планируете передавать файлы большого объема (десятки и сотни мегабайт), этот способ начнет оказывать серьезную нагрузку на оперативную память сервера и сетевой трафик.
Для оптимизации процесса рекомендуется отказаться от упаковки файла в JSON с Base64 и передавать двоичные данные напрямую в теле HTTP-запроса, используя метод УстановитьТелоИзФайла(). В этом случае метаданные (например, имя и расширение файла) передаются в коротких HTTP-заголовках, а тело запроса заполняется «сырыми» байтами файла:
// Пример оптимизированной отправки файла напрямую с диска
Запрос = Новый HTTPЗапрос(АдресРесурса);
// Передаем служебную информацию в заголовках
Запрос.Заголовки.Вставить("Content-Type", "application/octet-stream");
Запрос.Заголовки.Вставить("X-File-Extension", Выборка.Расширение);
Запрос.Заголовки.Вставить("X-File-Name", "document_draft");
// Потоковая передача файла с диска без полной загрузки в ОЗУ
Запрос.УстановитьТелоИзФайла(ПутьКФайлуНаДиске);
Результат = HTTPСоединение.ОтправитьДляОбработки(Запрос);
На стороне приемника (в HTTP-сервисе) такой файл можно прочитать напрямую в объект ДвоичныеДанные, обратившись к свойству Запрос.ПолучитьТелоКакДвоичныеДанные(), либо сразу сохранить поток на диск с помощью метода Запрос.ПолучитьТелоКакПоток().КопироватьВ().
Применяя данные подходы, вы гарантированно избавитесь от ошибок переполнения буфера заголовков, снизите нагрузку на серверную инфраструктуру и обеспечите стабильную работу интеграционных механизмов вашей системы 1С.