Обмен файлами между 1С и внешними системами по HTTP и SOAP
Содержание
Чтобы передать файл через веб-сервис 1С, прочитайте его в ДвоичныеДанные и положите в тело HTTPЗапрос — целиком через УстановитьТелоИзДвоичныхДанных() или прямо с диска через УстановитьИмяФайлаТела(). Затем отправьте запрос методом ОтправитьДляОбработки() объекта HTTPСоединение и проверьте код ответа. Для SOAP-сервиса файл передают параметром типа base64Binary: платформа сама превратит его в ДвоичныеДанные.
Код ниже работает в 1С:Предприятии 8.3 начиная с платформы 8.3.10, в любой конфигурации. Выполнять его нужно на сервере: HTTP-соединение с клиента тоже возможно, но тогда файл, права и прокси берутся с компьютера пользователя, а это неудобно для фонового обмена.
Какой способ передачи файла выбрать
Слово «веб-сервис» в 1С означает три разные вещи. От выбора зависит, как упаковывать файл.
| Способ | Где описан | Как передаётся файл | Когда подходит |
|---|---|---|---|
| Внешний REST API | HTTPСоединение + 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С. Путь
C:\Users\buh\Desktop\акт.pdfс компьютера бухгалтера сервер не видит. - Сформируйте заголовки: авторизация, тип содержимого, имя файла.
- Задайте тело:
УстановитьИмяФайлаТела()для файла на диске,УстановитьТелоИзДвоичныхДанных()для данных из реквизита или хранилища. - Отправьте запрос. Метод
POSTдаётОтправитьДляОбработки(), дляPUT—Записать(), для прочих —ВызватьHTTPМетод("PATCH", Запрос). - Проверьте код ответа, а не только отсутствие исключения. Исключение бывает лишь при сетевых сбоях, а ответ 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С?
Почему файл на стороне получателя открывается битым?
Можно ли передать сразу несколько файлов одним запросом?
ЗаписьZipФайла и отправьте архив.