Интеграция 1С с внешним REST-сервисом: соединение, токены, разбор ответа и ошибки

· 11 мин

Содержание

Чтобы работать с API в 1С, хватает трёх встроенных объектов платформы 8.3: HTTPСоединение открывает канал к серверу, HTTPЗапрос описывает адрес, заголовки и тело, HTTPОтвет возвращает код состояния и данные. JSON читают и пишут через ЧтениеJSON и ЗаписьJSON, HTTPS включают объектом ЗащищенноеСоединениеOpenSSL. Внешние библиотеки и COM-объекты вроде MSXML2.XMLHTTP для этого не нужны.

Если внешний сервис сам должен обращаться к 1С, задача обратная: тогда в конфигурации поднимают HTTP-сервис или включают стандартный интерфейс OData. Обе стороны интеграции разобраны ниже, с кодом, который можно взять за основу для своей разработки.

Как устроен HTTP-запрос к API из 1С

Схема всегда одна: создали соединение с хостом, собрали запрос к конкретному ресурсу, вызвали метод соединения, получили ответ. Соединение создают один раз и переиспользуют для серии запросов к тому же серверу.

HTTP-методМетод HTTPСоединениеКогда нужен
GETПолучить(Запрос)прочитать данные
POSTОтправитьДляОбработки(Запрос)создать объект, получить токен
PUTЗаписать(Запрос)заменить объект целиком
DELETEУдалить(Запрос)удалить объект
PATCH и любой другойВызватьHTTPМетод("PATCH", Запрос)частичное изменение
HEADПолучитьЗаголовки(Запрос)проверить ресурс без тела

Минимальный рабочий пример GET-запроса по HTTPS:

&НаСервереБезКонтекста
Функция ПолучитьЗаказ(НомерЗаказа, Токен)

    SSL = Новый ЗащищенноеСоединениеOpenSSL(, Новый СертификатыУдостоверяющихЦентровОС);
    Соединение = Новый HTTPСоединение("api.example.ru", 443, , , , 30, SSL);

    Заголовки = Новый Соответствие;
    Заголовки.Вставить("Accept", "application/json");
    Заголовки.Вставить("Authorization", "Bearer " + Токен);

    Запрос = Новый HTTPЗапрос("/v1/orders/" + КодироватьСтроку(НомерЗаказа,
        СпособКодированияСтроки.КодировкаURL), Заголовки);

    Ответ = Соединение.Получить(Запрос);
    Возврат Ответ;

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

Порядок параметров конструктора HTTPСоединение: сервер, порт, пользователь, пароль, прокси, таймаут в секундах, защищённое соединение. Хост пишут без https:// и без пути: путь уходит в HTTPЗапрос.

💡 Вызовы API делайте в серверном коде (&НаСервере, общий модуль с флагом «Сервер»). Так токены и пароли не попадают на клиент, а код одинаково работает в тонком клиенте, веб-клиенте и регламентном задании.

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

Авторизация и защищённое соединение

Способ авторизации задаёт документация сервиса. На практике встречаются четыре варианта.

СпособЧто передаёмГде хранить секрет
API-ключзаголовок вида X-API-Key: ...безопасное хранилище
BasicAuthorization: Basic <base64 логин:пароль>безопасное хранилище
Bearer-токенAuthorization: Bearer <токен>токен — в регистре с временем жизни
OAuth2 client credentialsсначала POST за токеном, потом Bearerclient_secret — в безопасном хранилище

Basic-авторизация

Заголовок надёжнее собрать вручную: одни серверы ждут его в первом же запросе, другие не присылают запрос авторизации повторно.

Строка64 = Base64Строка(ПолучитьДвоичныеДанныеИзСтроки(Логин + ":" + Пароль, КодировкаТекста.UTF8));
Строка64 = СтрЗаменить(СтрЗаменить(Строка64, Символы.ВК, ""), Символы.ПС, "");
Заголовки.Вставить("Authorization", "Basic " + Строка64);

Вторая строка не лишняя. Base64Строка разбивает длинный результат переносами строк, и сервер с таким заголовком отвечает 401.

OAuth2: получение токена

Самый частый поток для межсерверного обмена — client credentials. Тело отправляют в формате формы, а не JSON:

Функция ПолучитьТокенOAuth(ИдКлиента, СекретКлиента) Экспорт

    SSL = Новый ЗащищенноеСоединениеOpenSSL(, Новый СертификатыУдостоверяющихЦентровОС);
    Соединение = Новый HTTPСоединение("auth.example.ru", 443, , , , 30, SSL);

    Запрос = Новый HTTPЗапрос("/oauth/token");
    Запрос.Заголовки.Вставить("Content-Type", "application/x-www-form-urlencoded");

    Тело = "grant_type=client_credentials"
        + "&client_id=" + КодироватьСтроку(ИдКлиента, СпособКодированияСтроки.КодировкаURL)
        + "&client_secret=" + КодироватьСтроку(СекретКлиента, СпособКодированияСтроки.КодировкаURL);
    Запрос.УстановитьТелоИзСтроки(Тело, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);

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

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

    Результат = Новый Структура("Токен, ДействуетДо");
    Результат.Токен = Данные.Получить("access_token");
    Результат.ДействуетДо = ТекущаяУниверсальнаяДата() + Данные.Получить("expires_in");
    Возврат Результат;

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

Токен не запрашивают перед каждым вызовом. Сохраните его вместе со сроком действия, например в регистре сведений, и обновляйте заранее, примерно за минуту до окончания. Если сервис выдаёт refresh_token, обновляйте токен им, а не новым входом.

Где хранить ключи и пароли

  • 🔒 В конфигурациях на БСП есть безопасное хранилище: ОбщегоНазначения.ЗаписатьДанныеВБезопасноеХранилище и ОбщегоНазначения.ПрочитатьДанныеИзБезопасногоХранилища. Данные из него не попадают в обычные реквизиты и не видны пользователям без полных прав.
  • 🚫 Не пишите ключ константой в модуле: он окажется в выгрузке конфигурации, в хранилище и в каждой копии базы.
  • 🚫 Не храните секрет в реквизите справочника, который видят все: его легко выгрузить отчётом.

HTTPS и сертификаты

Новый СертификатыУдостоверяющихЦентровОС говорит платформе доверять корневым сертификатам из хранилища операционной системы. Если у сервиса сертификат внутреннего или самоподписанного центра, есть два правильных пути:

  1. Установить корневой сертификат этого центра в хранилище ОС на компьютере, где выполняется код (для клиент-серверной базы — на сервере 1С).
  2. Передать файл сертификата явно: Новый СертификатыУдостоверяющихЦентровФайл("C:\certs\root.cer").

Если сервис требует клиентский сертификат (взаимная аутентификация), его передают первым параметром: Новый ЗащищенноеСоединениеOpenSSL(Новый СертификатКлиентаФайл(ПутьКPfx, ПарольPfx), СертификатыЦентров).

Не отключайте проверку сертификата сервера в рабочей базе: без неё любой узел на пути трафика может подменить сервис и получить ваш токен.

JSON: собрать тело запроса и разобрать ответ

Отправка

Для объекта JSON используйте Соответствие, если в именах полей есть дефис, цифра в начале или другие символы, недопустимые в ключе Структура. Для простых имён подойдёт и структура.

Позиция = Новый Соответствие;
Позиция.Вставить("sku", "А-1024");
Позиция.Вставить("qty", 3);

Заказ = Новый Соответствие;
Заказ.Вставить("external-id", Строка(ДокументСсылка.УникальныйИдентификатор()));
Заказ.Вставить("date", ЗаписатьДатуJSON(Документ.Дата, ФорматДатыJSON.ISO));
Заказ.Вставить("items", Новый Массив);
Заказ["items"].Добавить(Позиция);

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

Запрос.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
Запрос.УстановитьТелоИзСтроки(ТекстJSON, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);

Ссылки на объекты 1С в JSON не сериализуются напрямую: передавайте уникальный идентификатор, код или номер. Пустое значение Неопределено записывается как null.

Параметр ИспользованиеByteOrderMark.НеИспользовать убирает метку BOM в начале тела. Часть серверов отвечает на неё 400 с невнятным текстом про некорректный JSON.

Разбор ответа

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

Для Каждого Строка Из Данные.Получить("items") Цикл
    Артикул = Строка.Получить("sku");
    Количество = Строка.Получить("qty");
КонецЦикла;

Второй параметр Истина превращает объекты JSON в Соответствие, а не в структуру. Так разбор не упадёт на поле с «неудобным» именем, а отсутствующее поле вернёт Неопределено вместо исключения.

Что учесть при разборе:

  • 📅 Даты в JSON приходят строкой. Преобразуйте их функцией ПрочитатьДатуJSON(Строка, ФорматДатыJSON.ISO).
  • 🔢 Числа приходят типом Число, но идентификаторы вроде "00123" сервис может прислать строкой. Не сравнивайте их без приведения типа.
  • ❓ Поле со значением null и отсутствующее поле в Соответствие дают одно и то же Неопределено. Если для логики разница важна, проверяйте наличие ключа.

Коды ответа и обработка сбоев

Ошибок у HTTP-вызова два уровня, и обрабатывают их по-разному.

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

Код = Ответ.КодСостояния;
Если Код >= 200 И Код < 300 Тогда
    // успех: разбираем тело
ИначеЕсли Код = 401 Тогда
    // токен устарел: получить новый и повторить запрос один раз
ИначеЕсли Код = 429 Или Код >= 500 Тогда
    // временная проблема сервиса: повторить позже
Иначе
    // 400, 403, 404, 409, 422: ошибка в данных или правах, повтор не поможет
    ЗаписьЖурналаРегистрации("Интеграция.Заказы", УровеньЖурналаРегистрации.Предупреждение, , ,
        Формат(Код, "ЧГ=0") + ": " + Ответ.ПолучитьТелоКакСтроку());
КонецЕсли;
КодЧто значитЧто делать в 1С
200, 201, 204успех; у 204 тела нету 204 не вызывать ПрочитатьJSON на пустой строке
400, 422сервис не принял данныезаписать тело ответа в журнал: там обычно список полей с ошибками
401нет или устарел токенобновить токен, повторить один раз
403токен верный, прав нетпроверить права ключа в кабинете сервиса
404нет ресурса или неверный путьсверить адрес с документацией
409конфликт, объект уже естьискать существующий объект, не создавать заново
429превышен лимит запросовждать время из заголовка Retry-After, если он есть
500, 502, 503, 504сбой на стороне сервисаповторить с увеличивающейся паузой

Повторы не делайте пустым циклом ожидания в том же сеансе: он держит рабочий процесс и блокировки. Правильнее записать неудачный вызов в очередь (регистр сведений со статусом и числом попыток) и обрабатывать её регламентным заданием, увеличивая интервал после каждой неудачи.

💡 Ключи Ответ.Заголовки чувствительны к регистру, а серверы пишут имена заголовков по-разному. Ищите нужный перебором с НРег(), а не прямым Получить("Retry-After").

Большие объёмы: страницы, файлы, фоновые задания

Когда сервис отдаёт десятки тысяч записей, узкие места три: память, время одного вызова и запись в базу.

  • 📄 Постраничная загрузка. Почти любой API поддерживает limit/offset или курсор. Запрашивайте порциями по 100–1000 записей и сразу записывайте их, не накапливая всё в массиве.
  • 💾 Ответ в файл. Метод Получить(Запрос, ИмяВыходногоФайла) пишет тело на диск, минуя строку в памяти. Файл потом читают через ЧтениеJSON.ОткрытьФайл(). Для очень больших массивов ЧтениеJSON умеет читать поток по одному элементу через Прочитать() и ТипТекущегоЗначения, не загружая документ целиком.
  • 🔁 Одно соединение. Создавайте HTTPСоединение один раз на весь цикл: так не тратится время на повторное TLS-рукопожатие.
  • ⏱ Таймаут по делу. 30 секунд хватает для обычных вызовов. Для тяжёлых выгрузок ставьте больше, но не ноль: без таймаута зависший сервис подвесит фоновое задание.
  • 🧵 Фоновые задания. Долгий обмен не запускайте в сеансе пользователя. В конфигурациях на БСП для этого есть модуль ДлительныеОперации, в остальных — ФоновыеЗадания.Выполнить().
  • 🧾 Транзакции порциями. Записывайте объекты пачками в отдельных транзакциях. Одна транзакция на 50 000 объектов надолго держит блокировки и откатывает всё при одной ошибке.
  • 🕓 Инкремент вместо полной выгрузки. Если API поддерживает фильтр по дате изменения, храните отметку последней успешной загрузки и запрашивайте только изменения.

Отладка и тестирование интеграции

Логируйте запрос и ответ

Пишите в журнал регистрации метод, адрес, код ответа и первые 1–2 тысячи символов тела. Токены и пароли перед записью вырезайте. С таким логом спор с поддержкой сервиса решается за минуты: видно, что именно ушло и что вернулось.

Повторите запрос вне 1С

Если сервис отвечает неожиданно, повторите тот же запрос через Postman или curl с тем же телом и заголовками:

curl -v -X POST "https://api.example.ru/v1/orders" ^
  -H "Authorization: Bearer ТОКЕН" ^
  -H "Content-Type: application/json" ^
  --data "@order.json"

Работает в curl, но не в 1С — ищите разницу в заголовках, кодировке и BOM. Не работает и там — вопрос к сервису или к данным.

Отладка серверного кода

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

Тесты без живого сервиса

  • 🧩 Разделите код на три функции: сборка запроса, вызов, разбор ответа. Сборку и разбор можно тестировать на сохранённых образцах JSON без сети.
  • 🧪 Для автотестов подойдут YAxUnit или Vanessa-ADD.
  • 🪞 Вместо боевого сервиса используйте песочницу провайдера или свою заглушку, которая отдаёт заранее подготовленные ответы, включая 401, 429 и 500.
Диагностика: выберите симптом

Когда API нужно открыть в самой 1С

Если внешняя система сама приходит за данными или присылает уведомления (вебхуки), 1С выступает сервером.

МеханизмЧто даётКогда выбрать
HTTP-сервиссвои адреса, методы и формат ответанужен собственный API под конкретную задачу, приём вебхуков
ODataготовый REST-доступ к справочникам, документам и регистрамвнешней системе нужно читать или писать объекты «как есть»
Web-сервис (SOAP)обмен по WSDLпартнёр работает только с SOAP

HTTP-сервис создают в Конфигураторе в ветке Общие → HTTP-сервисы: задают корневой URL, шаблоны адресов и обработчики методов. В типовой конфигурации его добавляют расширением, чтобы не снимать конфигурацию с поддержки. Затем базу публикуют на веб-сервере (Apache или IIS) через Администрирование → Публикация на веб-сервере в Конфигураторе. Адрес вызова будет вида https://сервер/имя_публикации/hs/корневой_url/....

Стандартный интерфейс OData отвечает по адресу .../odata/standard.odata/. Объекты, доступные через него, нужно явно включить в состав интерфейса, по умолчанию он пуст. Для обращений извне заведите отдельного пользователя 1С с минимальными правами.

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

Можно ли вызывать внешний API из клиентского модуля?
Технически можно, но секреты окажутся на клиенте, а в веб-клиенте добавятся ограничения браузера. Вызов делают на сервере, клиент только запускает его.
Как передать файл в multipart/form-data?
Готового объекта для multipart в платформе нет. Тело собирают вручную: границу (boundary), заголовки частей и двоичные данные файла склеивают через ЗаписьДанных в поток, а затем передают в запрос как двоичные данные.
Нужна ли отдельная лицензия 1С для обмена через HTTP?
Исходящие запросы выполняются в сеансе пользователя или фонового задания и отдельной лицензии не требуют. Входящие обращения к HTTP-сервису и OData открывают сеанс и расходуют лицензию на время вызова.
Как передать кириллицу в параметрах адреса?
Кодируйте значение функцией КодироватьСтроку со способом КодировкаURL. Иначе сервер получит искажённый параметр или вернёт 400.
Что выбрать для приёма вебхуков: HTTP-сервис или OData?
HTTP-сервис. Формат вебхука задаёт отправитель, и только в обработчике HTTP-сервиса можно разобрать его тело и ответить нужным кодом.