Обмен файлами между 1С и внешними системами по HTTP и SOAP

· 10 мин

Содержание

Чтобы передать файл через веб-сервис 1С, прочитайте его в ДвоичныеДанные и положите в тело HTTPЗапрос — целиком через УстановитьТелоИзДвоичныхДанных() или прямо с диска через УстановитьИмяФайлаТела(). Затем отправьте запрос методом ОтправитьДляОбработки() объекта HTTPСоединение и проверьте код ответа. Для SOAP-сервиса файл передают параметром типа base64Binary: платформа сама превратит его в ДвоичныеДанные.

Код ниже работает в 1С:Предприятии 8.3 начиная с платформы 8.3.10, в любой конфигурации. Выполнять его нужно на сервере: HTTP-соединение с клиента тоже возможно, но тогда файл, права и прокси берутся с компьютера пользователя, а это неудобно для фонового обмена.

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

Слово «веб-сервис» в 1С означает три разные вещи. От выбора зависит, как упаковывать файл.

СпособГде описанКак передаётся файлКогда подходит
Внешний REST APIHTTPСоединение + HTTPЗапроссырое тело, multipart или Base64 в JSONотправка в сторонний сервис, облако, сайт
HTTP-сервис 1Светка «HTTP-сервисы» в конфигураторесырое тело запроса и ответадругая система забирает или кладёт файл в базу 1С
SOAP-веб-сервис 1Светка «Web-сервисы», клиент через WSПроксипараметр base64Binary внутри XMLобмен между двумя базами 1С, старые интеграции

💡 Для новых интеграций берите HTTP-сервис. SOAP раздувает файл на треть из-за Base64 и держит весь XML в памяти. На файлах больше 20–30 МБ это заметно.

Подготовка: публикация и HTTP-соединение

Проверьте до первой строки кода:

  • 🌐 Сервер доступен. С сервера 1С (не с вашего ноутбука) должен открываться адрес сервиса. Обмен выполняется от имени процесса rphost, у него свои прокси и брандмауэр.
  • 🔐 Сертификат. Для HTTPS нужен объект ЗащищенноеСоединениеOpenSSL. Если сервер подписан корпоративным центром сертификации, передайте корневые сертификаты ОС.
  • 📤 Публикация базы. Если файл принимает сама 1С, база публикуется на веб-сервере: в конфигураторе Администрирование → Публикация на веб-сервере. Отметьте нужные HTTP-сервисы или Web-сервисы на соответствующих вкладках.
  • 👤 Пользователь для обмена. Отдельный пользователь 1С с ролью, где разрешены только нужные объекты. Не используйте администратора.

Создание соединения:

ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL(, Новый СертификатыУдостоверяющихЦентровОС);

Соединение = Новый HTTPСоединение(
	"api.example.ru",       // сервер без https://
	443,                    // порт
	"exchange",             // пользователь
	"пароль",               // пароль
	,                       // прокси
	120,                    // таймаут в секундах
	ЗащищенноеСоединение);

Таймаут ставьте с запасом. Значение 0 означает «ждать бесконечно», и зависший сервер повесит фоновое задание навсегда.

Если сервер требует не логин, а токен, оставьте пользователя и пароль пустыми и добавьте заголовок Authorization в соответствие заголовков запроса.

Как упаковать файл в HTTP-запрос

У одного и того же файла три формы. Внешний API обычно сам диктует, какую ждёт.

Сырое тело (application/octet-stream)

Самый простой и экономный вариант. Тело запроса — это байты файла и ничего больше. Имя файла передают в заголовке или в адресе ресурса.

Заголовки = Новый Соответствие;
Заголовки.Вставить("Content-Type", "application/octet-stream");
Заголовки.Вставить("X-File-Name", КодироватьСтроку(ИмяФайла, СпособКодированияСтроки.КодировкаURL));

Запрос = Новый HTTPЗапрос("/api/upload", Заголовки);
Запрос.УстановитьИмяФайлаТела(ПутьКФайлу);

Кириллица в заголовке без кодирования превратится в «кракозябры» на стороне получателя. Поэтому имя проходит через КодироватьСтроку().

Base64 внутри JSON

Подходит, когда вместе с файлом нужно передать реквизиты: номер договора, контрагента, тип документа.

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

Пакет = Новый Структура;
Пакет.Вставить("fileName", ИмяФайла);
Пакет.Вставить("content", СтрокаBase64);

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

Запрос.УстановитьТелоИзСтроки(Запись.Закрыть(), КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);

Base64Строка() вставляет переносы строк. Многие API на них спотыкаются и отвечают «invalid base64». Поэтому в примере переносы удаляются.

multipart/form-data

Так браузер отправляет файл из HTML-формы. Многие сайты и облачные сервисы принимают файлы только так. Тело собирается из трёх частей: заголовок части, байты файла, закрывающая граница.

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

Начало = "--" + Граница + ПС
	+ "Content-Disposition: form-data; name=""file""; filename=""" + ИмяФайла + """" + ПС
	+ "Content-Type: application/octet-stream" + ПС + ПС;
Конец = ПС + "--" + Граница + "--" + ПС;

Части = Новый Массив;
Части.Добавить(ПолучитьДвоичныеДанныеИзСтроки(Начало, "UTF-8"));
Части.Добавить(Новый ДвоичныеДанные(ПутьКФайлу));
Части.Добавить(ПолучитьДвоичныеДанныеИзСтроки(Конец, "UTF-8"));

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

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

Значение name="file" берите из документации API: если сервер ждёт поле document, с именем file он ответит 400. Разделители строк в multipart — строго ВК + ПС, одиночный ПС часть серверов не принимает.

Отправка файла на внешний сервер по шагам

Соберём всё в одну серверную функцию с проверкой результата.

Функция ОтправитьФайл(ПутьКФайлу, ИмяФайла) Экспорт

	Соединение = Новый HTTPСоединение("api.example.ru", 443, , , , 120,
		Новый ЗащищенноеСоединениеOpenSSL());

	Заголовки = Новый Соответствие;
	Заголовки.Вставить("Authorization", "Bearer " + ПолучитьТокенОбмена());
	Заголовки.Вставить("Content-Type", "application/octet-stream");
	Заголовки.Вставить("X-File-Name", КодироватьСтроку(ИмяФайла, СпособКодированияСтроки.КодировкаURL));

	Запрос = Новый HTTPЗапрос("/api/upload", Заголовки);
	Запрос.УстановитьИмяФайлаТела(ПутьКФайлу);

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

	Если Ответ.КодСостояния < 200 Или Ответ.КодСостояния > 299 Тогда
		ЗаписьЖурналаРегистрации("Обмен.ОтправкаФайла", УровеньЖурналаРегистрации.Ошибка, , ,
			СтрШаблон("Код %1: %2", Формат(Ответ.КодСостояния, "ЧГ=0"), Ответ.ПолучитьТелоКакСтроку()));
		Возврат Ложь;
	КонецЕсли;

	Возврат Истина;

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

ПолучитьТокенОбмена() — ваша функция, которая читает токен из безопасного хранилища. Не храните токен в тексте модуля.

Порядок действий:

  1. Убедитесь, что файл лежит на диске сервера 1С. Путь C:\Users\buh\Desktop\акт.pdf с компьютера бухгалтера сервер не видит.
  2. Сформируйте заголовки: авторизация, тип содержимого, имя файла.
  3. Задайте тело: УстановитьИмяФайлаТела() для файла на диске, УстановитьТелоИзДвоичныхДанных() для данных из реквизита или хранилища.
  4. Отправьте запрос. Метод POST даёт ОтправитьДляОбработки(), для PUT — Записать(), для прочих — ВызватьHTTPМетод("PATCH", Запрос).
  5. Проверьте код ответа, а не только отсутствие исключения. Исключение бывает лишь при сетевых сбоях, а ответ 401 или 500 приходит «успешно».

Если файл выбирает пользователь

Когда пользователь выбирает файл на своём компьютере, его сначала нужно доставить на сервер. На клиенте вызовите НачатьПомещениеФайла() с идентификатором формы: файл уйдёт во временное хранилище и вернёт адрес. На сервере данные забирают так:

&НаСервере
Процедура ОтправитьНаСервере(АдресВоВременномХранилище, ИмяФайла)
	ДанныеФайла = ПолучитьИзВременногоХранилища(АдресВоВременномХранилище);
	ВременныйПуть = ПолучитьИмяВременногоФайла("bin");
	ДанныеФайла.Записать(ВременныйПуть);
	ОтправитьФайл(ВременныйПуть, ИмяФайла);
	УдалитьФайлы(ВременныйПуть);
КонецПроцедуры

Большие файлы: память, части и контрольная сумма

Файл на 300 МБ, загруженный в ДвоичныеДанные, занимает 300 МБ памяти рабочего процесса. В Base64 — ещё треть сверху, а в JSON-строке — вдвое больше. На загруженном сервере это приводит к падению rphost с потерей сеансов пользователей.

Три приёма спасают положение:

  • 📁 Тело из файла. УстановитьИмяФайлаТела() читает файл с диска при отправке, не держа его целиком в памяти. Работает только для сырого тела.
  • 📥 Ответ в файл. У методов Получить() и ОтправитьДляОбработки() есть второй параметр — имя выходного файла. Ответ пишется сразу на диск.
  • ✂️ Деление на части. Глобальная функция РазделитьФайл() режет файл на куски заданного размера и возвращает массив имён. Получатель собирает их через ОбъединитьФайлы().

Отправка по частям по 10 МБ:

Части = РазделитьФайл(ПутьКФайлу, 10 * 1024 * 1024);
Всего = Части.Количество();

Для Индекс = 0 По Всего - 1 Цикл
	Заголовки = Новый Соответствие;
	Заголовки.Вставить("Content-Type", "application/octet-stream");
	Заголовки.Вставить("X-Upload-Id", ИдентификаторЗагрузки);
	Заголовки.Вставить("X-Chunk", СтрШаблон("%1/%2", Формат(Индекс + 1, "ЧГ=0"), Формат(Всего, "ЧГ=0")));

	Запрос = Новый HTTPЗапрос("/api/upload/chunk", Заголовки);
	Запрос.УстановитьИмяФайлаТела(Части[Индекс]);
	Ответ = Соединение.ОтправитьДляОбработки(Запрос);
	УдалитьФайлы(Части[Индекс]);

	Если Ответ.КодСостояния <> 200 Тогда
		ВызватьИсключение СтрШаблон("Часть %1 не принята, код %2",
			Формат(Индекс + 1, "ЧГ=0"), Формат(Ответ.КодСостояния, "ЧГ=0"));
	КонецЕсли;
КонецЦикла;

Индексы в цикле идут с нуля, в заголовке нумерация с единицы: 10-мегабайтный кусок файла на 25 МБ даст 3 части — «1/3», «2/3», «3/3».

Протокол частей (имена заголовков, адрес) придумываете вы или он задан в API. Если API частичную загрузку не поддерживает, делить бессмысленно: сервер получит три разных файла.

Проверяйте целостность: после сборки сравните хеш. На стороне 1С он считается так:

Хеш = Новый ХешированиеДанных(ХешФункция.SHA256);
Хеш.ДобавитьФайл(ПутьКФайлу);
Контроль = НРег(ПолучитьHexСтрокуИзДвоичныхДанных(Хеш.ХешСумма));

Передайте Контроль в заголовке последней части. Получатель сверит хеш собранного файла и откажет, если байт потерялся.

💡 «Многопоточность» в смысле параллельной отправки частей делают фоновыми заданиями: каждое задание шлёт свою часть. Начинайте с 2–4 потоков, больше редко ускоряет, зато нагружает сервер.

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

Теперь обратная сторона: файл приходит в вашу базу.

Приём через HTTP-сервис

В конфигураторе создайте HTTP-сервис, корневой URL files, шаблон /upload/{name}, метод POST с обработчиком:

Функция ЗагрузитьФайлPOST(Запрос)

	ИмяФайла = РаскодироватьСтроку(Запрос.ПараметрыURL["name"], СпособКодированияСтроки.КодировкаURL);
	ДанныеФайла = Запрос.ПолучитьТелоКакДвоичныеДанные();

	Если ДанныеФайла.Размер() = 0 Тогда
		Возврат Новый HTTPСервисОтвет(400);
	КонецЕсли;

	СохранитьВходящийФайл(ИмяФайла, ДанныеФайла); // ваша процедура: справочник файлов, присоединённые файлы
	Возврат Новый HTTPСервисОтвет(201);

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

После публикации сервис доступен по адресу вида https://сервер/имя_базы/hs/files/upload/akt.pdf.

Отдача файла

Обработчик метода GET возвращает файл телом ответа:

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

Если файл хранится на диске сервера, вместо двоичных данных используйте Ответ.УстановитьИмяФайлаТела(Путь) — меньше памяти.

С другой базы 1С такой файл забирают так:

Запрос = Новый HTTPЗапрос("/имя_базы/hs/files/download/" + Идентификатор);
ВыходнойФайл = ПолучитьИмяВременногоФайла("pdf");
Ответ = Соединение.Получить(Запрос, ВыходнойФайл);

Через SOAP-веб-сервис

В ветке «Web-сервисы» добавьте операцию, например PutFile, с параметрами FileName (тип string) и Content (тип base64Binary из пространства имён http://www.w3.org/2001/XMLSchema). В обработчике Content уже будет ДвоичныеДанные.

Вызов с другой базы:

Определения = Новый WSОпределения("https://сервер/имя_базы/ws/files?wsdl", "exchange", "пароль");
Прокси = Новый WSПрокси(Определения, "http://example.ru/files", "Files", "FilesSoap");
Прокси.Пользователь = "exchange";
Прокси.Пароль = "пароль";
Результат = Прокси.PutFile(ИмяФайла, Новый ДвоичныеДанные(ПутьКФайлу));

Пространство имён, имя сервиса и точки подключения берите из WSDL. Ошибка в одной букве даёт исключение при создании прокси.

Диагностика ошибок и журнал обмена

Почти все сбои передачи файла видны по тексту исключения или коду ответа.

Диагностика: выберите симптом

Что писать в журнал

Пишите одно событие на попытку обмена: имя файла, размер, код ответа, длительность. Для этого подходит ЗаписьЖурналаРегистрации() с собственным именем события, например Обмен.ОтправкаФайла. Потом в журнале регистрации ставите отбор по событию и видите всю историю.

  • ✅ Размер в байтах до и после передачи — сразу видно обрезанный файл.
  • ✅ Первые 500–1000 символов тела ответа при ошибке. Целиком не надо: сервер может вернуть HTML-страницу на 2 МБ.
  • ❌ Содержимое файла и токены не пишите. Журнал читают люди без доступа к этим данным.

💡 Если код ответа непонятен, отправьте тот же запрос через curl с сервера 1С. Ответил curl — проблема в коде 1С, нет — в сети или на стороне получателя.

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

Какой максимальный размер файла можно передать через веб-сервис 1С?
Жёсткого лимита у платформы нет. Ограничивают память рабочего процесса и настройки веб-сервера. На практике файлы больше 50–100 МБ передают частями или сырым телом из файла.
Почему файл на стороне получателя открывается битым?
Обычно тело собрано как строка, а не как двоичные данные, или в multipart добавлены лишние символы. Сравните размер и хеш до и после передачи.
Можно ли передать сразу несколько файлов одним запросом?
Да: в multipart добавьте несколько частей с одной границей и разными именами полей, либо упакуйте файлы в zip через ЗаписьZipФайла и отправьте архив.
Работает ли такой обмен в облачной 1С или в безопасном режиме?
В расширении с безопасным режимом исходящие HTTP-запросы требуют разрешения через профиль безопасности. В облачных сервисах обычно разрешён только HTTPS к внешним адресам.
Чем HTTP-сервис лучше SOAP для передачи файлов?
Он передаёт байты без Base64, умеет отдавать тело прямо с диска и проще вызывается из любых языков. SOAP оправдан там, где обмен уже построен на WSDL.