framework/skills/bsl-practices/integration-patterns/SKILL.md
Паттерны интеграции 1С: HTTP/REST/SOAP сервисы, аутентификация (Basic/Token/OAuth/CertificateAuth), идемпотентность, retry, безопасное хранение секретов, версионирование. Использовать когда нужно создать HTTP-сервис, REST/SOAP-клиент, реализовать webhook, описать контракт, настроить аутентификацию или обработку ошибок внешнего взаимодействия.
npx skillsauth add steelmorgan/1c-agent-based-dev-framework integration-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Ключевой принцип: Контракт определяется до кода. HTTP-сервис — тонкий слой разбора запроса и формирования ответа; бизнес-логика живёт в общих модулях. Секреты — только в БезопасноеХранилище, никогда в коде или константах.
Перед написанием кода зафиксируйте контракт в виде структуры или комментария:
Изменение контракта без версионирования — нарушение совместимости. Добавляйте новые поля, не удаляя старые. Смену семантики существующих полей оформляйте через новую версию (/v2/…).
Обработчик 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.Закрыть());
Возврат Ответ;
КонецФункции
Все секреты (токены, пароли, ключи) хранятся исключительно в БезопасноеХранилище. Никогда — в константах конфигурации, параметрах сеанса, реквизитах справочников или текстах модулей.
Подробно по каждой схеме — см. references/auth-schemes.md.
// Получение учётных данных из безопасного хранилища
УчётныеДанные = БезопасноеХранилище.Прочитать("ИнтеграцияСВнешнимСервисом");
Логин = УчётныеДанные.Логин;
Пароль = УчётныеДанные.Пароль;
// Не логируйте: Логин/Пароль, Заголовок Authorization, тело с персданными
Соединение = Новый HTTPСоединение(
"api.example.com",
, // порт — по умолчанию 443 для HTTPS
Логин,
Пароль,
, // прокси
30, // таймаут (сек)
Новый ЗащищённоеСоединение); // OpenSSL / CertificateAuth — см. ниже
// Токен из безопасного хранилища
ТокенДоступа = БезопасноеХранилище.Прочитать("ИнтеграцияСВнешнимСервисом").ТокенДоступа;
Запрос = Новый HTTPЗапрос("/api/v1/resource");
Запрос.Заголовки.Вставить("Authorization", "Bearer " + ТокенДоступа);
Запрос.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
// Путь к сертификату и пароль — из безопасного хранилища
ПараметрыCertAuth = БезопасноеХранилище.Прочитать("ИнтеграцияСертификат");
СертификатКлиента = Новый СертификатКлиентаФайл(
ПараметрыCertAuth.ПутьКСертификату,
ПараметрыCertAuth.Пароль);
ЗащИтоеСоединение = Новый ЗащищённоеСоединение(
, , СертификатКлиента, ,
Истина); // проверять серверный сертификат
Соединение = Новый HTTPСоединение(
"api.example.com", , , , , 30, ЗащищённоеСоединение);
Внешний вызов ненадёжен. Всегда оборачивайте в Попытка/Исключение. Для изменяющих операций используйте идемпотентный ключ и защиту от повторного выполнения.
Функция ВызватьВнешний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'"),
НомерПопытки, МаксимумПопыток,
ПодробноеПредставлениеОшибки(ИнфоОшибки)));
Если НомерПопытки = МаксимумПопыток Тогда
ВызватьИсключение;
КонецЕсли;
// Пауза перед следующей попыткой
ТекущаяДата = ТекущаяДата();
Пока ТекущаяДата() < ТекущаяДата + ОжиданиеМеждуПопытками Цикл
КонецЦикла;
КонецПопытки;
КонецЦикла;
Возврат "";
КонецФункции
Изменяющие операции (POST создание, изменение состояния) должны быть защищены от двойного выполнения.
Паттерны идемпотентности:
Idempotency-Key в заголовке (UUID, сформированный на стороне клиента);НомерВнешнего) в теле с уникальным индексом на стороне 1С;// Проверка дубля перед записью (серверная идемпотентность)
Функция НайтиЗаказПоВнешнемуНомеру(НомерВнешнего)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Заказы.Ссылка КАК Ссылка
|ИЗ
| Документ.ЗаказКлиента КАК Заказы
|ГДЕ
| Заказы.НомерВнешнего = &НомерВнешнего";
Запрос.УстановитьПараметр("НомерВнешнего", НомерВнешнего);
Выборка = Запрос.Выполнить().Выбрать();
Возврат ?(Выборка.Следующий(), Выборка.Ссылка, Неопределено);
КонецФункции
// В обработчике POST:
СуществующийЗаказ = НайтиЗаказПоВнешнемуНомеру(ПараметрыЗаказа.НомерВнешнего);
Если СуществующийЗаказ <> Неопределено Тогда
// Дубль — вернуть 200 с данными существующего заказа (не 201)
Возврат ОтветУспеха(200, ПолучитьДанныеЗаказа(СуществующийЗаказ));
КонецЕсли;
Ответ об ошибке должен быть предсказуемым. Клиент должен уметь отличить:
| 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.
Для работы с 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 = 'ПлатёжныйШлюз.Оплата'"),
УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
correlationId, requestId, X-Correlation-Id);Authorization (Bearer-токен, Basic-credentials);БезопасноеХранилище;// Корреляция через запрос
КорреляцияИд = Запрос.Заголовки.Получить("X-Correlation-Id");
Если НЕ ЗначениеЗаполнено(КорреляцияИд) Тогда
КорреляцияИд = Строка(Новый УникальныйИдентификатор);
КонецЕсли;
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ИнтеграцияЗаказов.ПолучитьЗаказ'"),
УровеньЖурналаРегистрации.Информация,,,
СтрШаблон(НСтр("ru = 'correlationId=%1 result=OK orderId=%2'"),
КорреляцияИд, НомерЗаказа));
Проверяйте аутентификацию в первую очередь, до обращения к бизнес-данным.
// Проверка Bearer-токена во входящем запросе
Функция ПроверитьАутентификациюЗапроса(Запрос)
ЗаголовокAuth = Запрос.Заголовки.Получить("Authorization");
Если НЕ ЗначениеЗаполнено(ЗаголовокAuth) Тогда
Возврат Ложь;
КонецЕсли;
Если НЕ СтрНачинаетсяС(ЗаголовокAuth, "Bearer ") Тогда
Возврат Ложь;
КонецЕсли;
ПолученныйТокен = Сред(ЗаголовокAuth, 8); // убираем "Bearer "
ОжидаемыйТокен = БезопасноеХранилище.Прочитать("ВходящийAPIТокен").Токен;
// Сравнение в постоянное время (защита от timing attack)
// Для простых случаев допустимо прямое сравнение строк
Возврат (ПолученныйТокен = ОжидаемыйТокен);
КонецФункции
// В начале обработчика:
Если НЕ ПроверитьАутентификациюЗапроса(Запрос) Тогда
Возврат ОтветОшибки(401, "UNAUTHORIZED", НСтр("ru = 'Аутентификация не прошла'"));
КонецЕсли;
Изменения контракта без обратной совместимости требуют новой версии.
| Изменение | Совместимость | Действие |
|-----------|--------------|---------|
| Добавить новое поле в ответ | Совместимо | Добавить; документировать |
| Добавить необязательное поле в запрос | Совместимо | Добавить с 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 | | Ревью кода интеграции | Пройти по чеклисту ниже |
БезопасноеХранилище; нигде не логируются/v2/…)tools
Diagnostics for Vanessa Automation runs. Use when a feature scenario failed, artifacts were not created, or you need to classify a failure after launch.
tools
Creating and refining Vanessa Automation feature scenarios based on real project requirements. Use when you need to write or update a scenario test, not just run it.
tools
--- name: v8-session-manager description: Use when working with the 1С session manager (v8-session-manager) - launch, configuration, connecting 1С clients, reading session_list, calling proxied MCP-tools from 1С extensions, diagnostics. Triggers: mention of `v8-session-manager`, `session_list`, 1С extension MCP showcase, error “no active sessions” / “session_id required”, connecting a client to the manager via `mcpMode=ws`. provides_capabilities: # Built-in manager tools — always available whi
tools
Use when Codex needs to manage v8-runner on local 1C projects through the CLI: configure v8project.yaml, initialize infobases or EDT workspaces, build sources from Designer or EDT, run syntax checks and tests, dump infobase changes, convert source formats, load or export artifacts, launch 1C clients, or choose safe 1C automation command sequences.