При разработке механизмов интеграции и автоматизации в системе 1С:Предприятие часто возникает задача разделения логики выполнения кода в зависимости от того, кто является инициатором вызова. Например, при интерактивной работе пользователя в документе должны формироваться одни движения, а при загрузке данных через стандартный интерфейс oData — другие (или вовсе отключаться). В этой статье мы подробно разберем, почему для этой задачи нельзя использовать стандартные директивы препроцессора и как правильно идентифицировать oData-запросы программным способом.
Разберем фундаментальный механизм работы платформы. Многие разработчики привыкли использовать конструкции типа #Если ВнешнееСоединение Тогда или #Если Сервер Тогда. Однако важно понимать, что директивы препроцессора отрабатывают в момент компиляции модуля, а не в момент его исполнения (runtime).
Проанализируем ситуацию: когда сервер 1С компилирует программный код (например, МодульОбъекта документа), он создает исполняемый двоичный код для контекста Сервер. На этом этапе платформа не знает и не может знать, какой именно запрос вызовет этот код через секунду — поступит ли он от тонкого клиента, от регламентного задания или через REST API (oData). Для всех этих случаев используется один и тот же скомпилированный блок кода с директивой #Если Сервер Тогда.
Следовательно, выделить oData на уровне препроцессора невозможно, так как это потребовало бы наличия отдельного контекста компиляции, которого для oData в архитектуре 1С не предусмотрено. oData — это не отдельный вид клиента (как COM-соединение), а технология доступа к данным, работающая внутри серверного контекста.
Выяснив, что директивы нам не помогут, рассмотрим наиболее надежный и правильный способ определения контекста во время выполнения кода. Для этого мы будем использовать свойства текущего сеанса информационной базы. Рассмотрим подробнее функцию ПолучитьТекущийСеансИнформационнойБазы().
У объекта сеанса есть свойство ИмяПриложения, которое однозначно идентифицирует тип соединения. Для стандартного интерфейса oData платформа всегда устанавливает значение этого свойства в строку "OData".
Разберем пример кода, который можно разместить в обработчике ПередЗаписью модуля объекта:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Определяем, вызвано ли выполнение через oData
ТекущийСеанс = ПолучитьТекущийСеансИнформационнойБазы();
Если ТекущийСеанс.ИмяПриложения = "OData" Тогда
// Логика специально для oData
// Например, отключаем проведение или заполняем доп. реквизиты
ЭтотОбъект.ДополнительныеСвойства.Вставить("ВыполненоЧерезOData", Истина);
Иначе
// Стандартная логика для пользователей и других сервисов
КонецЕсли;
КонецПроцедуры
Проанализируем, как oData взаимодействует с сессиями на сервере 1С. Запросы к стандартному интерфейсу используют так называемые VRS-соединения (Virtual Resource System). В консоли кластера серверов такие соединения отображаются отдельно.
Важный момент для разработчика заключается в том, что oData-запросы по своей природе являются stateless (не хранят состояние). Это означает, что на каждый HTTP-запрос платформа может выделять сеанс из пула, выполнять код и возвращать его обратно. Поэтому попытки использовать ПараметрыСеанса для фиксации признака "это oData" могут работать нестабильно, если вы не инициализируете их в специализированном модуле Сессия или в обработчике УстановкаПараметровСеанса.
Если вы решите использовать ПараметрыСеанса, логика должна быть следующей:
Процедура УстановкаПараметровСеанса(ТребуемыеПараметры)
ИмяПриложения = ПолучитьТекущийСеансИнформационнойБазы().ИмяПриложения;
Если ИмяПриложения = "OData" Тогда
ПараметрыСеанса.ЭтоИнтерфейсOData = Истина;
КонецЕсли;
КонецПроцедуры
Часто возникает путаница: почему oData не считается внешним соединением? Давайте выясним причину. Внешнее соединение (COM) — это отдельный технологический стек, который имеет свой собственный контекст компиляции (тот самый #Если ВнешнееСоединение). Он не использует веб-сервер для передачи команд.
oData же работает поверх протокола HTTP через публикацию на веб-сервере (Apache или IIS). В этом плане она ближе к самописным HTTP-сервисам. Однако есть различие в ИмяПриложения:
Посмотрим на пример, как можно универсально проверять "неинтерактивность" вызова, если мы хотим объединить проверку на oData и фоновые задания:
Функция ЭтоРоботизированныйВызов() Экспорт
ИмяПриложения = ПолучитьТекущийСеансИнформационнойБазы().ИмяПриложения;
Возврат (ИмяПриложения = "OData")
ИЛИ (ИмяПриложения = "BackgroundJob") // Фоновое задание
ИЛИ (ИмяПриложения = "SystemBackgroundJob"); // Системное фоновое задание
КонецФункции
Вернемся к задаче, озвученной в обсуждении: нужно лишать документ возможности делать движения, если он пришел через oData. Поскольку мы выяснили, что директив препроцессора нет, мы используем проверку в ОбработкаПроведения.
Важно: Платформа 1С при выполнении POST или PATCH запроса через oData может автоматически вызывать проведение, если в URL передан соответствующий параметр. Чтобы заблокировать это поведение на уровне кода, используйте следующий подход:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
Если ПолучитьТекущийСеансИнформационнойБазы().ИмяПриложения = "OData" Тогда
// Если мы не хотим, чтобы oData-интерфейс мог проводить документы
Сообщить("Проведение документов через oData запрещено правилами системы");
Отказ = Истина;
Возврат;
КонецЕсли;
// Основная логика проведения...
КонецПроцедуры
Подведем итоги нашего анализа:
#Если...) для oData не существует, так как это рантайм-свойство сеанса, а не контекст компиляции модуля.ПолучитьТекущийСеансИнформационнойБазы().ИмяПриложения = "OData".ПараметрыСеанса при старте сессии.Используя данные подходы, вы сможете гибко управлять поведением системы, обеспечивая корректную обработку данных как при ручном вводе пользователями, так и при автоматизированном обмене через современные REST-интерфейсы.