Интеграция 1С с внешним REST-сервисом: соединение, токены, разбор ответа и ошибки
Содержание
Чтобы работать с 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: ... | безопасное хранилище |
| Basic | Authorization: Basic <base64 логин:пароль> | безопасное хранилище |
| Bearer-токен | Authorization: Bearer <токен> | токен — в регистре с временем жизни |
| OAuth2 client credentials | сначала POST за токеном, потом Bearer | client_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С).
- Передать файл сертификата явно:
Новый СертификатыУдостоверяющихЦентровФайл("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-вызова два уровня, и обрабатывают их по-разному.
- Ответа нет совсем: не найден хост, оборвалось соединение, истёк таймаут, не прошла проверка сертификата. Платформа вызывает исключение,
HTTPОтветне создаётся. - Ответ есть, но с кодом ошибки: исключения нет, всё решает
Ответ.КодСостояния.
Попытка
Ответ = Соединение.Получить(Запрос);
Исключение
ЗаписьЖурналаРегистрации("Интеграция.Заказы", УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Возврат Неопределено; // сетевой сбой: поставить задание на повтор
КонецПопытки;
Код = Ответ.КодСостояния;
Если Код >= 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С с минимальными правами.