Skill v1.0.1
Automated scan100/100+2 new
version: "1.0.1" name: integration-patterns description: "Use for проектирования HTTP-сервисов и интеграций 1С (REST/SOAP/webhook). Helps зафиксировать контракт до кода, реализовать аутентификацию (Basic/OAuth/CertificateAuth), retry и безопасное хранение секретов."
Паттерны интеграции 1С
Ключевой принцип: Контракт определяется до кода. HTTP-сервис — тонкий слой разбора запроса и формирования ответа; бизнес-логика живёт в общих модулях. Секреты — только в БезопасноеХранилище, никогда в коде или константах.
Правило 1: Контракт — первое, что определяется
Перед написанием кода зафиксируйте контракт в виде структуры или комментария:
- транспорт и URL (метод, версия в пути, заголовки Content-Type/Accept);
- аутентификация (схема, где хранится секрет);
- тело запроса и ответа (поля, типы, обязательность, null-семантика);
- ключ идемпотентности (если операция меняет данные);
- retry-политика и таймаут;
- форма ошибки (коды HTTP и структура тела ошибки).
Изменение контракта без версионирования — нарушение совместимости. Добавляйте новые поля, не удаляя старые. Смену семантики существующих полей оформляйте через новую версию (/v2/…).
Правило 2: HTTP-сервисы 1С — тонкий обработчик
Обработчик HTTP-сервиса должен делать только три вещи: разобрать запрос, вызвать бизнес-функцию, вернуть ответ. Не кладите бизнес-логику прямо в обработчик.
Канонический паттерн HTTP-сервиса
// Обработчик метода POST ресурса /ordersФункция ОбработатьПОСТ(Запрос)// 1. Разбор телаТелоСтрокой = Запрос.ПолучитьТелоКакСтроку();ЧтениеJSON = Новый ЧтениеJSON;ЧтениеJSON.УстановитьСтроку(ТелоСтрокой);ПараметрыЗаказа = ПрочитатьJSON(ЧтениеJSON, Тип("Структура"));// 2. Валидация обязательных полейЕсли НЕ ЗначениеЗаполнено(ПараметрыЗаказа.НомерВнешнего) ТогдаВозврат ОтветОшибки(400, "VALIDATION_ERROR",НСтр("ru = 'Поле НомерВнешнего обязательно'"));КонецЕсли;// 3. Бизнес-вызовПопыткаРезультатСозданияЗаказа = ИнтеграцияЗаказов.СоздатьЗаказ(ПараметрыЗаказа);ИсключениеЗаписьЖурналаРегистрации(НСтр("ru = 'ИнтеграцияЗаказов.СоздатьЗаказ'"),УровеньЖурналаРегистрации.Ошибка,,,ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));Возврат ОтветОшибки(500, "INTERNAL_ERROR",НСтр("ru = 'Внутренняя ошибка сервера'"));КонецПопытки;// 4. Формирование ответаВозврат ОтветУспеха(201, РезультатСозданияЗаказа);КонецФункции// Вспомогательные функции формирования ответаФункция ОтветОшибки(КодСостояния, КодОшибки, Сообщение)Ответ = Новый HTTPСервисОтвет(КодСостояния);Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");СтруктураОшибки = Новый Структура("error, message", КодОшибки, Сообщение);ЗаписьJSON = Новый ЗаписьJSON;ЗаписьJSON.УстановитьСтроку();ЗаписатьJSON(ЗаписьJSON, СтруктураОшибки);Ответ.УстановитьТелоИзСтроки(ЗаписьJSON.Закрыть());Возврат Ответ;КонецФункцииФункция ОтветУспеха(КодСостояния, Данные)Ответ = Новый HTTPСервисОтвет(КодСостояния);Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");ЗаписьJSON = Новый ЗаписьJSON;ЗаписьJSON.УстановитьСтроку();ЗаписатьJSON(ЗаписьJSON, Данные);Ответ.УстановитьТелоИзСтроки(ЗаписьJSON.Закрыть());Возврат Ответ;КонецФункции
Правило 3: Аутентификация — схемы и хранение секретов
Все секреты (токены, пароли, ключи) хранятся исключительно в БезопасноеХранилище. Никогда — в константах конфигурации, параметрах сеанса, реквизитах справочников или текстах модулей.
Подробно по каждой схеме — см. references/auth-schemes.md.
Basic Auth
// Получение учётных данных из безопасного хранилищаУчётныеДанные = БезопасноеХранилище.Прочитать("ИнтеграцияСВнешнимСервисом");Логин = УчётныеДанные.Логин;Пароль = УчётныеДанные.Пароль;// Не логируйте: Логин/Пароль, Заголовок Authorization, тело с персданнымиСоединение = Новый HTTPСоединение("api.example.com",, // порт — по умолчанию 443 для HTTPSЛогин,Пароль,, // прокси30, // таймаут (сек)Новый ЗащищённоеСоединение); // OpenSSL / CertificateAuth — см. ниже
Bearer Token (API-ключ или OAuth 2.0 access_token)
// Токен из безопасного хранилищаТокенДоступа = БезопасноеХранилище.Прочитать("ИнтеграцияСВнешнимСервисом").ТокенДоступа;Запрос = Новый HTTPЗапрос("/api/v1/resource");Запрос.Заголовки.Вставить("Authorization", "Bearer " + ТокенДоступа);Запрос.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
Аутентификация по сертификату (TLS mutual auth / CertificateAuth)
// Путь к сертификату и пароль — из безопасного хранилищаПараметрыCertAuth = БезопасноеХранилище.Прочитать("ИнтеграцияСертификат");СертификатКлиента = Новый СертификатКлиентаФайл(ПараметрыCertAuth.ПутьКСертификату,ПараметрыCertAuth.Пароль);ЗащИтоеСоединение = Новый ЗащищённоеСоединение(, , СертификатКлиента, ,Истина); // проверять серверный сертификатСоединение = Новый HTTPСоединение("api.example.com", , , , , 30, ЗащищённоеСоединение);
Правило 4: HTTP-клиент — retry и таймаут
Внешний вызов ненадёжен. Всегда оборачивайте в Попытка/Исключение. Для изменяющих операций используйте идемпотентный ключ и защиту от повторного выполнения.
Функция ВызватьВнешнийAPIСПовтором(URLПуть, ТелоЗапросаJSON, КлючИдемпотентности = "")МаксимумПопыток = 3;ОжиданиеМеждуПопытками = 2; // секундыДля НомерПопытки = 1 По МаксимумПопыток ЦиклПопыткаСоединение = Новый HTTPСоединение("api.example.com", , , , , 30,Новый ЗащищённоеСоединение);Запрос = Новый HTTPЗапрос(URLПуть);Запрос.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");// Ключ идемпотентности — безопасное повторение без дублированияЕсли ЗначениеЗаполнено(КлючИдемпотентности) ТогдаЗапрос.Заголовки.Вставить("Idempotency-Key", КлючИдемпотентности);КонецЕсли;Запрос.УстановитьТелоИзСтроки(ТелоЗапросаJSON, КодировкаТекста.UTF8);Ответ = Соединение.ОтправитьДляОбработки(Запрос);Если Ответ.КодСостояния >= 200 И Ответ.КодСостояния < 300 ТогдаВозврат Ответ.ПолучитьТелоКакСтроку();ИначеЕсли Ответ.КодСостояния >= 400 И Ответ.КодСостояния < 500 Тогда// Клиентская ошибка — не ретраитьВызватьИсключение СтрШаблон(НСтр("ru = 'Ошибка запроса (HTTP %1). Повтор нецелесообразен.'"),Ответ.КодСостояния);ИначеЕсли Ответ.КодСостояния >= 500 Тогда// Серверная ошибка — ретраимВызватьИсключение СтрШаблон(НСтр("ru = 'Сервер вернул %1'"), Ответ.КодСостояния);КонецЕсли;ИсключениеИнфоОшибки = ИнформацияОбОшибке();УровеньЖР = ?(НомерПопытки < МаксимумПопыток,УровеньЖурналаРегистрации.Предупреждение,УровеньЖурналаРегистрации.Ошибка);ЗаписьЖурналаРегистрации(НСтр("ru = 'ВнешняяИнтеграция.HTTPЗапрос'"), УровеньЖР, , ,СтрШаблон(НСтр("ru = 'Попытка %1/%2. Ошибка: %3'"),НомерПопытки, МаксимумПопыток,ПодробноеПредставлениеОшибки(ИнфоОшибки)));Если НомерПопытки = МаксимумПопыток ТогдаВызватьИсключение;КонецЕсли;// Пауза перед следующей попыткойТекущаяДата = ТекущаяДата();Пока ТекущаяДата() < ТекущаяДата + ОжиданиеМеждуПопытками ЦиклКонецЦикла;КонецПопытки;КонецЦикла;Возврат "";КонецФункции
Правило 5: Идемпотентность — защита от дублей
Изменяющие операции (POST создание, изменение состояния) должны быть защищены от двойного выполнения.
Паттерны идемпотентности:
Idempotency-Keyв заголовке (UUID, сформированный на стороне клиента);- внешний идентификатор (
НомерВнешнего) в теле с уникальным индексом на стороне 1С; - проверка существования объекта перед созданием.
// Проверка дубля перед записью (серверная идемпотентность)Функция НайтиЗаказПоВнешнемуНомеру(НомерВнешнего)Запрос = Новый Запрос;Запрос.Текст ="ВЫБРАТЬ ПЕРВЫЕ 1| Заказы.Ссылка КАК Ссылка|ИЗ| Документ.ЗаказКлиента КАК Заказы|ГДЕ| Заказы.НомерВнешнего = &НомерВнешнего";Запрос.УстановитьПараметр("НомерВнешнего", НомерВнешнего);Выборка = Запрос.Выполнить().Выбрать();Возврат ?(Выборка.Следующий(), Выборка.Ссылка, Неопределено);КонецФункции// В обработчике POST:СуществующийЗаказ = НайтиЗаказПоВнешнемуНомеру(ПараметрыЗаказа.НомерВнешнего);Если СуществующийЗаказ <> Неопределено Тогда// Дубль — вернуть 200 с данными существующего заказа (не 201)Возврат ОтветУспеха(200, ПолучитьДанныеЗаказа(СуществующийЗаказ));КонецЕсли;
Правило 6: Форма ошибки — стабильный контракт
Ответ об ошибке должен быть предсказуемым. Клиент должен уметь отличить:
| HTTP-код | Тип ошибки | Поведение клиента | |
|---|---|---|---|
| 400 | Ошибка валидации входных данных | Не ретраить; исправить запрос | |
| 401 | Ошибка аутентификации | Обновить токен; не ретраить немедленно | |
| 409 | Конфликт / дубль | Не ретраить; обработать дубль | |
| 422 | Бизнес-ошибка (данные корректны, но нарушено правило) | Не ретраить; показать пользователю | |
| 500 | Внутренняя ошибка | Ретраить с backoff | |
| 503 | Сервис временно недоступен | Ретраить с backoff |
// Единая структура тела ошибки// {"error": "VALIDATION_ERROR", "message": "...", "correlationId": "..."}Функция ОтветОшибкиС Корреляцией(КодСостояния, КодОшибки, Сообщение, КорреляцияИд)Ответ = Новый HTTPСервисОтвет(КодСостояния);Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");Ответ.Заголовки.Вставить("X-Correlation-Id", КорреляцияИд);ТелоОшибки = Новый Структура;ТелоОшибки.Вставить("error", КодОшибки);ТелоОшибки.Вставить("message", Сообщение);ТелоОшибки.Вставить("correlationId", КорреляцияИд);ЗаписьJSON = Новый ЗаписьJSON;ЗаписьJSON.УстановитьСтроку();ЗаписатьJSON(ЗаписьJSON, ТелоОшибки);Ответ.УстановитьТелоИзСтроки(ЗаписьJSON.Закрыть());Возврат Ответ;КонецФункции
Никогда не возвращайте стек вызовов 1С, имена модулей или внутренние ID объектов метаданных во внешний API.
Правило 7: SOAP / WSПрокси
Для работы с SOAP-сервисами используйте WSПрокси, создаваемый через WSОпределения. Аутентификацию передавайте через параметры прокси, не в теле сообщения.
// Создание WSПрокси с аутентификациейФункция СоздатьПроксиПлатёжногоШлюза()УчётныеДанные = БезопасноеХранилище.Прочитать("ПлатёжныйШлюзSOAP");WSОпределения = Новый WSОпределения("https://payment.example.com/service?wsdl",УчётныеДанные.Логин,УчётныеДанные.Пароль,, // прокси30); // таймаутПрокси = WSОпределения.СоздатьWSПрокси("PaymentService", "PaymentPort");Прокси.Пользователь = УчётныеДанные.Логин;Прокси.Пароль = УчётныеДанные.Пароль;Прокси.Таймаут = 30;Возврат Прокси;КонецФункции// Вызов с обработкой ошибокПопыткаПроксиWS = СоздатьПроксиПлатёжногоШлюза();// XDTO-объект для тела запросаФабрикаXDTO = ПроксиWS.ФабрикаXDTO;ЗапросXDTO = ФабрикаXDTO.Создать(ФабрикаXDTO.Тип("http://payment.example.com/", "PayRequest"));ЗапросXDTO.Amount = СуммаПлатежа;ЗапросXDTO.OrderId = Строка(ИдентификаторЗаказа);ЗапросXDTO.Currency = "RUB";ОтветXDTO = ПроксиWS.Pay(ЗапросXDTO);Если ОтветXDTO.Status <> "OK" ТогдаВызватьИсключение СтрШаблон(НСтр("ru = 'Платёжный шлюз вернул: %1'"), ОтветXDTO.Status);КонецЕсли;ИсключениеЗаписьЖурналаРегистрации(НСтр("ru = 'ПлатёжныйШлюз.Оплата'"),УровеньЖурналаРегистрации.Ошибка,,,ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));ВызватьИсключение;КонецПопытки;
Правило 8: Логирование — что писать и что скрывать
Пишите в журнал регистрации
- Идентификатор корреляции (
correlationId,requestId,X-Correlation-Id); - внешний идентификатор бизнес-объекта (номер заказа, ID платежа);
- HTTP-код ответа и время выполнения запроса;
- номер попытки при retry;
- краткое описание результата (создан/обновлён/отклонён).
Никогда не пишите в журнал
- Значения заголовка
Authorization(Bearer-токен, Basic-credentials); - пароли, ключи API, секреты из
БезопасноеХранилище; - полное тело запроса/ответа, если оно содержит персональные данные;
- внутренние стеки вызовов в ответах клиенту (только в ЖР).
// Корреляция через запросКорреляцияИд = Запрос.Заголовки.Получить("X-Correlation-Id");Если НЕ ЗначениеЗаполнено(КорреляцияИд) ТогдаКорреляцияИд = Строка(Новый УникальныйИдентификатор);КонецЕсли;ЗаписьЖурналаРегистрации(НСтр("ru = 'ИнтеграцияЗаказов.ПолучитьЗаказ'"),УровеньЖурналаРегистрации.Информация,,,СтрШаблон(НСтр("ru = 'correlationId=%1 result=OK orderId=%2'"),КорреляцияИд, НомерЗаказа));
Правило 9: HTTP-сервис 1С — аутентификация входящих запросов
Проверяйте аутентификацию в первую очередь, до обращения к бизнес-данным.
// Проверка Bearer-токена во входящем запросеФункция ПроверитьАутентификациюЗапроса(Запрос)ЗаголовокAuth = Запрос.Заголовки.Получить("Authorization");Если НЕ ЗначениеЗаполнено(ЗаголовокAuth) ТогдаВозврат Ложь;КонецЕсли;Если НЕ СтрНачинаетсяС(ЗаголовокAuth, "Bearer ") ТогдаВозврат Ложь;КонецЕсли;ПолученныйТокен = Сред(ЗаголовокAuth, 8); // убираем "Bearer "ОжидаемыйТокен = БезопасноеХранилище.Прочитать("ВходящийAPIТокен").Токен;// Сравнение в постоянное время (защита от timing attack)// Для простых случаев допустимо прямое сравнение строкВозврат (ПолученныйТокен = ОжидаемыйТокен);КонецФункции// В начале обработчика:Если НЕ ПроверитьАутентификациюЗапроса(Запрос) ТогдаВозврат ОтветОшибки(401, "UNAUTHORIZED", НСтр("ru = 'Аутентификация не прошла'"));КонецЕсли;
Правило 10: Версионирование интерфейса
Изменения контракта без обратной совместимости требуют новой версии.
| Изменение | Совместимость | Действие | |
|---|---|---|---|
| Добавить новое поле в ответ | Совместимо | Добавить; документировать | |
| Добавить необязательное поле в запрос | Совместимо | Добавить с defaults | |
| Удалить или переименовать поле | Несовместимо | Создать /v2/…, поддерживать /v1/… | |
| Изменить тип поля | Несовместимо | Создать /v2/…, поддерживать /v1/… | |
| Изменить семантику существующего поля | Несовместимо | Создать /v2/… |
Версию указывайте в URL: /api/v1/orders, /api/v2/orders.
Типичные ошибки
| Ошибка | Последствие | Как избежать | |
|---|---|---|---|
| Секрет в константе конфигурации или реквизите | Утечка при выгрузке конфигурации / базы | Только БезопасноеХранилище | |
| HTTP-вызов внутри транзакции | Таймаут (30 с) = блокировка всех связанных записей | Выносить HTTP-вызовы за пределы транзакции | |
| Нет ключа идемпотентности при повторах | Дублирование данных при сетевой ошибке | Использовать Idempotency-Key или внешний ID с уникальным индексом | |
| Возврат стека 1С в теле ошибки | Раскрытие внутреннего устройства системы | Возвращать только стабильный код ошибки и сообщение | |
| Ретрай на 4xx-ошибках | Бесполезная нагрузка, возможное повторение конфликта | Ретраить только 5xx и сетевые ошибки | |
| Логирование токенов/паролей | Секреты в журнале регистрации | Маскировать перед записью в ЖР | |
| Бизнес-логика в обработчике HTTP-сервиса | Невозможность повторного использования и тестирования | Тонкий обработчик + отдельный общий модуль | |
| Нет валидации входных данных | Запись некорректных данных в базу | Проверять все обязательные поля до бизнес-операции |
Когда применять
| Триггер | Действие | |
|---|---|---|
| Создаётся HTTP-сервис 1С (входящий) | Применить правила 2, 6, 9, 10 | |
| Создаётся HTTP-клиент (исходящий REST) | Применить правила 3, 4, 5, 8 | |
| Настраивается SOAP/Web Service | Применить правило 7, 3 (секреты) | |
| Проектируется или меняется контракт | Начать с правила 1, применить правило 10 | |
| Добавляется любая аутентификация | Применить правило 3 + references/auth-schemes.md | |
| Ревью кода интеграции | Пройти по чеклисту ниже |
Чеклист ревью интеграции
- [ ] Контракт описан: endpoint, метод, версия, auth-схема, payload, error shape
- [ ] Секреты только в
БезопасноеХранилище; нигде не логируются - [ ] HTTP-вызовы вынесены за пределы транзакций 1С
- [ ] Изменяющие операции защищены от дублей (ключ идемпотентности или внешний ID)
- [ ] Обработчик HTTP-сервиса тонкий: parse → validate → call → respond
- [ ] Ответ об ошибке стабилен; стек вызовов не возвращается клиенту
- [ ] Retry только на 5xx и сетевых ошибках
- [ ] Логируется correlationId, но не токен/пароль
- [ ] Несовместимые изменения контракта оформлены как новая версия (
/v2/…)
Связанные ресурсы
- references/auth-schemes.md — детали по каждой схеме аутентификации
- bsl-practices/error-handling/SKILL.md — транзакции и обработка исключений
depends_on: []