Как принять и отдать файл через HTTP-сервис в 1С:Предприятии 8.3

· 9 мин

Содержание

Файл передают через HTTP-сервис 1С так: клиент шлёт его в теле POST-запроса, а обработчик метода забирает содержимое через Запрос.ПолучитьТелоКакДвоичныеДанные() и сохраняет. Обратно файл отдают объектом HTTPСервисОтвет с телом из двоичных данных и заголовками Content-Type и Content-Disposition.

Типичная ситуация: сайт или мобильное приложение склада должно загрузить в 1С скан накладной, а бухгалтерия хочет забирать из базы готовые PDF счетов. Ниже — рабочий код для обеих сторон, варианты с Base64 и multipart и то, на чём такие сервисы обычно ломаются в эксплуатации.

Как HTTP-сервис встроен в 1С и где он слушает запросы

HTTP-сервис — это объект конфигурации в ветке Общие → HTTP-сервисы. Он превращает базу в маленький веб-API: внешняя система обращается по адресу, 1С выполняет код и возвращает ответ. Механизм общий для платформы 8.3 и не зависит от конфигурации — БП, УТ или своя разработка.

Структура объекта:

  • 🧩 Корневой URL — часть адреса после /hs/, например files.
  • 🧭 Шаблоны URL — пути внутри сервиса: /upload, /download/{id}. Фигурные скобки задают параметры.
  • ⚙️ Методы — для каждого шаблона свой HTTP-метод (GET, POST, PUT, DELETE) и своя функция-обработчик в модуле сервиса.

Полный адрес выглядит так: http://сервер/имя_публикации/hs/files/upload.

Чтобы сервис отвечал, базу публикуют на веб-сервере (Apache или IIS) через Администрирование → Публикация на веб-сервере в Конфигураторе. В окне публикации есть вкладка с HTTP-сервисами: проверьте, что ваш сервис отмечен. После изменения конфигурации публикацию обычно не трогают, но новые сервисы при выключенном флажке «публиковать по умолчанию» придётся отметить вручную.

Обработчик метода выполняется на сервере 1С от имени пользователя, который прошёл аутентификацию. Права этого пользователя определяют, к каким данным код получит доступ.

Приём файла в теле POST-запроса

Самый простой и надёжный способ — «сырое» тело. Клиент кладёт байты файла целиком в тело запроса, а имя и тип передаёт отдельно: в параметре URL или в заголовке.

Шаблон URL: /upload, метод POST, обработчик UploadPOST.

Функция UploadPOST(Запрос)

    ИмяФайла = Запрос.ПараметрыЗапроса.Получить("name");
    Если Не ЗначениеЗаполнено(ИмяФайла) Тогда
        Возврат ОтветОшибка(400, "Не передан параметр name");
    КонецЕсли;

    Данные = Запрос.ПолучитьТелоКакДвоичныеДанные();
    Если Данные = Неопределено Или Данные.Размер() = 0 Тогда
        Возврат ОтветОшибка(400, "Пустое тело запроса");
    КонецЕсли;

    // Пример: сохраняем во временный файл на сервере
    ПутьКФайлу = ПолучитьИмяВременногоФайла("bin");
    Данные.Записать(ПутьКФайлу);

    // Здесь же можно записать данные в реквизит типа ХранилищеЗначения:
    // Объект.Файл = Новый ХранилищеЗначения(Данные, Новый СжатиеДанных(9));

    Ответ = Новый HTTPСервисОтвет(201);
    Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
    Ответ.УстановитьТелоИзСтроки("{""status"":""ok"",""size"":"
        + Формат(Данные.Размер(), "ЧГ=0") + "}");
    Возврат Ответ;

КонецФункции

Функция ОтветОшибка(Код, Текст)
    Ответ = Новый HTTPСервисОтвет(Код);
    Ответ.Заголовки.Вставить("Content-Type", "text/plain; charset=utf-8");
    Ответ.УстановитьТелоИзСтроки(Текст);
    Возврат Ответ;
КонецФункции

Что пригодится из объекта HTTPСервисЗапрос:

Свойство или методЧто даёт
ПолучитьТелоКакДвоичныеДанные()всё тело в памяти — для файлов до десятков мегабайт
ПолучитьТелоКакПоток()поток для чтения частями — для крупных файлов
ПолучитьТелоКакСтроку()тело как текст — только для JSON и XML, не для бинарных файлов
Заголовкисоответствие заголовков, например Content-Type
ПараметрыЗапросапараметры после ? в адресе
ПараметрыURLзначения из {id} в шаблоне

Имя файла с кириллицей в параметре URL клиент обязан закодировать (percent-encoding). Платформа раскодирует его сама, и в ПараметрыЗапроса придёт обычная строка.

Не читайте бинарный файл через ПолучитьТелоКакСтроку(). Байты, которые не складываются в символы UTF-8, испортятся, и PDF или картинка не откроются.

Отдача файла из 1С по GET-запросу

Обратная задача — клиент запрашивает файл, 1С отдаёт его как скачивание. Шаблон URL: /download/{id}, метод GET.

Функция DownloadGET(Запрос)

    Идентификатор = Запрос.ПараметрыURL.Получить("id");
    Данные = НайтиФайлПоИдентификатору(Идентификатор); // ваша функция
    Если Данные = Неопределено Тогда
        Возврат ОтветОшибка(404, "Файл не найден");
    КонецЕсли;

    ИмяФайла = "Счёт " + Идентификатор + ".pdf";
    ИмяURL = КодироватьСтроку(ИмяФайла, СпособКодированияСтроки.КодировкаURL);

    Ответ = Новый HTTPСервисОтвет(200);
    Ответ.Заголовки.Вставить("Content-Type", "application/pdf");
    Ответ.Заголовки.Вставить("Content-Disposition",
        "attachment; filename*=UTF-8''" + ИмяURL);
    Ответ.УстановитьТелоИзДвоичныхДанных(Данные);
    Возврат Ответ;

КонецФункции

Три детали, из-за которых файл скачивается «не так»:

  1. 📄 Content-Type. Без него браузер может показать PDF как текст. Для неизвестного типа ставьте application/octet-stream.
  2. 🔤 Имя с кириллицей. Форма filename*=UTF-8''… с закодированной строкой понимается современными браузерами. Обычный filename="Счёт.pdf" без кодирования часто превращается в набор знаков.
  3. 📦 Крупные файлы. Если файл уже лежит на диске сервера, вызовите Ответ.УстановитьИмяФайлаТела(ПутьКФайлу) вместо загрузки в память. Платформа отдаст его сама.

Если нужно отдать файл из присоединённых файлов типовой конфигурации, двоичные данные получают тем же кодом, что и при обычном сохранении файла на диск: через хранилище значения или том хранения. Конкретная функция зависит от версии БСП в базе — откройте модуль работы с файлами и найдите, как там получают двоичные данные.

Файл внутри JSON: кодирование Base64

Когда файл нужно передать вместе с реквизитами одним запросом, его кодируют в Base64 и кладут строкой в JSON:

{
  "document": "ПТ-000154",
  "fileName": "накладная.jpg",
  "content": "/9j/4AAQSkZJRgABAQEASABIAAD..."
}

Приём на стороне 1С:

Функция UploadJsonPOST(Запрос)

    Чтение = Новый ЧтениеJSON;
    Чтение.УстановитьСтроку(Запрос.ПолучитьТелоКакСтроку());
    Пакет = ПрочитатьJSON(Чтение);
    Чтение.Закрыть();

    Данные = Base64Значение(Пакет.content);
    Если Данные = Неопределено Тогда
        Возврат ОтветОшибка(400, "Поле content не в формате Base64");
    КонецЕсли;

    // Данные — обычные ДвоичныеДанные, дальше как в примере с POST
    Возврат Новый HTTPСервисОтвет(201);

КонецФункции

Отправка из 1С во внешнюю систему — обратное действие: Base64Строка(Данные). Строка, которую формирует 1С, может содержать переносы строк. Строгие парсеры на других языках их не любят, поэтому перед записью в JSON уберите их:

Строка64 = СтрЗаменить(Base64Строка(Данные), Символы.ВК, "");
Строка64 = СтрЗаменить(Строка64, Символы.ПС, "");

Когда Base64 оправдан, а когда нет:

СпособКогда подходитМинусы
Сырое тело POSTодин файл, любой размерметаданные — только в URL или заголовках
Base64 в JSONфайл + реквизиты, небольшие файлыобъём растёт примерно на треть, всё держится в памяти
multipart/form-dataвеб-формы, несколько файлов сразуна стороне 1С разбор пишется вручную

Ориентир по размеру: файл 15 МБ в Base64 занимает около 20 МБ, а при чтении через строку и JSON в памяти рабочего процесса одновременно живут несколько копий. Для сканов и PDF до нескольких мегабайт это нормально, для архивов в сотни мегабайт — нет.

Multipart/form-data: разбор и отправка

Формат multipart присылают HTML-формы и многие библиотеки по умолчанию. Тело делится на части строкой-разделителем, которая указана в заголовке: Content-Type: multipart/form-data; boundary=----abc123.

Готового разборщика multipart в платформе нет. Разбор пишут на объекте ЧтениеДанных, который умеет искать маркер в потоке. Идея: найти разделитель, прочитать заголовки части до пустой строки, затем прочитать содержимое до следующего разделителя.

Функция ПрочитатьПервыйФайл(Запрос)

    ТипКонтента = Запрос.Заголовки.Получить("Content-Type");
    Поз = СтрНайти(ТипКонтента, "boundary=");
    Если Поз = 0 Тогда
        Возврат Неопределено;
    КонецЕсли;
    Граница = СокрЛП(Сред(ТипКонтента, Поз + 9));
    Граница = СтрЗаменить(Граница, """", "");

    Чтение = Новый ЧтениеДанных(Запрос.ПолучитьТелоКакПоток());
    Чтение.ПропуститьДо("--" + Граница);           // начало первой части
    Чтение.ПропуститьДо(Символы.ВК + Символы.ПС + Символы.ВК + Символы.ПС); // конец заголовков части
    Результат = Чтение.ПрочитатьДо(Символы.ВК + Символы.ПС + "--" + Граница);
    Чтение.Закрыть();

    Возврат Результат.ПолучитьДвоичныеДанные();

КонецФункции

Пример берёт первую часть и не читает её заголовки. В рабочем коде прочитайте заголовки части как строку и возьмите из Content-Disposition значения name и filename, а затем повторяйте цикл, пока не встретится закрывающий разделитель --граница--.

Отправить multipart из 1С проще, чем разобрать. Тело собирают в памяти через ЗаписьДанных:

Граница = СтрЗаменить(Новый УникальныйИдентификатор, "-", "");
ПС = Символы.ВК + Символы.ПС;

Поток = Новый ПотокВПамяти;
Запись = Новый ЗаписьДанных(Поток);
Запись.ЗаписатьСтроку("--" + Граница + ПС);
Запись.ЗаписатьСтроку("Content-Disposition: form-data; name=""file""; filename=""scan.pdf""" + ПС);
Запись.ЗаписатьСтроку("Content-Type: application/pdf" + ПС + ПС);
Запись.Записать(ДанныеФайла);
Запись.ЗаписатьСтроку(ПС + "--" + Граница + "--" + ПС);
Запись.Закрыть();

HTTPЗапрос = Новый HTTPЗапрос("/api/upload");
HTTPЗапрос.Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + Граница);
HTTPЗапрос.УстановитьТелоИзДвоичныхДанных(Поток.ЗакрытьИПолучитьДвоичныеДанные());

Если внешняя система ваша и формат можно выбрать, не принимайте multipart в 1С. Сырое тело с именем в параметре URL даёт тот же результат без ручного разбора.

Какой способ передачи файла выбрать?

Безопасность, лимиты и обработка ошибок

Сервис, который принимает файлы, — открытая дверь в базу. Минимум, который стоит сделать до запуска:

  • 🔐 Аутентификация. Заведите отдельного пользователя 1С с ролью только на нужные объекты. Не публикуйте сервис под учётной записью администратора и не прописывайте её логин в файле публикации.
  • 🔒 HTTPS. Basic-аутентификация передаёт пароль в открытом виде, если канал не зашифрован. Сертификат настраивают на веб-сервере.
  • 📏 Размер. Проверяйте Данные.Размер() и возвращайте 413, если файл больше вашего лимита.
  • 🧪 Тип. Не доверяйте расширению из имени. Проверьте первые байты: PDF начинается с %PDF, ZIP и DOCX — с PK.
  • 🗂 Имя файла. Не используйте присланное имя как путь на диске сервера: в нём может оказаться ..\. Сохраняйте под своим именем, присланное храните в реквизите.

Лимиты на размер тела стоят до 1С, на веб-сервере и прокси:

ГдеПараметрЗначение по умолчанию
IISmaxAllowedContentLength (фильтрация запросов)около 30 МБ
nginx перед Apache/IISclient_max_body_size1 МБ
ApacheLimitRequestBodyзависит от версии, проверьте конфигурацию

Если запрос «падает» с 413 или 404.13 ещё до вашего кода, дело именно в этих настройках.

Ошибки в самом обработчике оборачивайте в Попытка … Исключение. Иначе клиент получит 500 со служебным текстом платформы, а вы не узнаете причину:

Попытка
    СохранитьФайл(Данные, ИмяФайла);
Исключение
    ЗаписьЖурналаРегистрации("HTTP.ПриёмФайла", УровеньЖурналаРегистрации.Ошибка,,,
        ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
    Возврат ОтветОшибка(500, "Не удалось сохранить файл");
КонецПопытки;

Подробный текст ошибки пишите в журнал регистрации, а клиенту отдавайте короткое сообщение. Стек вызовов и имена модулей наружу отдавать незачем.

Коды ответов, которые стоит различать: 400 — нет параметра или битый Base64, 401 — не прошла аутентификация (её возвращает сама платформа), 404 — нет файла, 413 — превышен размер, 500 — сбой при сохранении.

Проверка сервиса через curl

Прежде чем подключать внешнюю систему, проверьте сервис из командной строки. curl есть в Windows 10 и новее и в любом Linux.

Отправка файла сырым телом:

curl -u "obmen:пароль" -X POST \
  -H "Content-Type: application/pdf" \
  --data-binary "@scan.pdf" \
  "https://srv/buh/hs/files/upload?name=scan.pdf"

Флаг --data-binary важен: обычный -d удаляет переводы строк и портит бинарный файл.

Отправка как веб-форма (multipart):

curl -u "obmen:пароль" -F "file=@scan.pdf" "https://srv/buh/hs/files/upload-form"

Скачивание с сохранением под именем из ответа:

curl -u "obmen:пароль" -OJ "https://srv/buh/hs/files/download/154"

Если ответ — HTML-страница веб-сервера, а не ваш текст, запрос не дошёл до 1С. Проверьте адрес публикации, корневой URL сервиса и отметку сервиса в настройках публикации.

Частые вопросы

Можно ли отладить HTTP-сервис в отладчике Конфигуратора?
Да. Включите отладку на веб-сервере в настройках публикации, подключитесь из Конфигуратора и поставьте точку останова в обработчике метода. Затем отправьте запрос через curl или Postman.
Почему вместо моего ответа приходит 401?
Платформа не пустила запрос: неверный логин или пароль, у пользователя нет прав на сервис, или клиент не передал заголовок авторизации. Код обработчика при этом не выполняется.
Как передать несколько файлов за один запрос?
Либо multipart с несколькими частями, либо JSON с массивом объектов, где у каждого своё имя и поле Base64. Для больших файлов надёжнее отправлять их по одному.
Сохраняется ли сеанс 1С между запросами к сервису?
По умолчанию каждый вызов работает в отдельном сеансе. Повторное использование сеансов настраивают в свойствах HTTP-сервиса в конфигурации, но данные между вызовами всё равно храните в базе, а не в переменных модуля.
Где хранить принятый файл?
В реквизите типа хранилище значения или в присоединённых файлах документа. Временный файл на диске сервера удаляйте сразу после переноса в базу.