Каждый разработчик на платформе 1С:Предприятие привык доверять конструкции Попытка ... Исключение как надежному инструменту для обработки непредвиденных ситуаций во время выполнения кода. Будь то деление на ноль, обращение к несуществующему реквизиту объекта или сетевой сбой — мы ожидаем, что управление гарантированно перейдет в блок Исключение, где мы сможем корректно обработать ошибку. Однако на практике существуют критические ситуации, способные обойти этот защитный барьер и привести к аварийному завершению работы приложения. В рамках данной статьи мы подробно проанализируем именно такую ситуацию на примере вызова конструктора объекта HTTPЗапрос, выясним архитектурные причины этого поведения и выработаем надежные методы защиты нашего кода.
Представим стандартный сценарий: нам необходимо инициализировать объект HTTPЗапрос для последующей отправки данных на внешний веб-сервер. В процессе интеграции данные могут поступать из внешних источников в непредсказуемом формате. Если вместо ожидаемой строки в качестве параметра адреса передается значение типа Неопределено, логично предположить, что платформа вызовет исключение, которое мы легко перехватим стандартными методами. Рассмотрим тестовый код, наглядно демонстрирующий аномальное поведение платформы на сервере:
&НаСервере
Процедура ВыполнитьТестированиеНаСервере()
// Попытка инициализировать объект с некорректным типом параметра
Попытка
// Передаем Неопределено вместо строки адреса
Запрос = Новый HTTPЗапрос(Неопределено);
Сообщить("Объект успешно создан!"); // Сюда код дойти не должен
Исключение
Сообщить("Успешно поймали ошибку в блоке Исключение!");
КонецПопытки;
КонецПроцедуры
Запустив этот код на стороне сервера в клиент-серверном варианте работы, мы обнаружим критическую проблему: выполнение кода прерывается ошибкой несоответствия типов, но управление не переходит в локальный блок Исключение. Вместо этого ошибка «Несоответствие типов» пробивает локальный стек обработки и уходит на самый верхний уровень, прерывая выполнение всей процедуры. Почему же так происходит?
Для того чтобы понять природу этой проблемы, нам необходимо заглянуть под капот платформы 1С:Предприятие 8. Архитектура платформы разделена на несколько уровней:
HTTPЗапрос, HTTPСоединение, а также механизмы интеграции вроде COMОбъект.Когда во встроенном языке мы пишем Новый HTTPЗапрос(Неопределено), происходит следующее:
Неопределено с сигнатурой конструктора нативного класса на C++.Неопределено к строке невозможно, на уровне ядра (в C++ коде) генерируется критическая ошибка несоответствия типов (Type Mismatch).ОшибкаИспользованияВстроенногоЯзыка и передать ее виртуальной машине для обработки в блоке Исключение.rphost.exe) трансляция этого низкоуровневого исключения нарушается. Происходит аварийное прерывание текущего потока выполнения или некорректная раскрутка стека (Stack Unwinding) нативного кода. В худшем случае это может приводить к скрытому падению рабочего процесса сервера приложений.Поскольку поток выполнения прерывается на уровне нативного ядра без возврата управления в виртуальную машину встроенного языка, локальная конструкция Попытка ... Исключение физически не успевает сработать. Для виртуальной машины выполнение модуля экстренно завершается.
Ситуация с конструктором HTTPЗапрос не уникальна. Разберем другие распространенные «непроверяемые» сценарии и системные сбои, которые невозможно обработать с помощью стандартных языковых средств 1С:
Попытка и завершает работу всего модуля.Новый COMОбъект управление передается стороннему коду. Если внутри DLL-библиотеки происходит критическая ошибка обращения к памяти (например, обращение по нулевому указателю), операционная система принудительно завершает рабочий процесс 1С (или процесс-хост). Конструкция Попытка не способна предотвратить падение процесса ОС.Попытки — платформа принудительно выполнит полный откат всех изменений до самого верхнего уровня.rphost.exe принудительно перезапускается. Обработать эту ситуацию программно внутри кода невозможно.Поскольку мы выяснили, что полагаться исключительно на блок Попытка ... Исключение при инициализации системных объектов нельзя, единственным надежным способом защиты является превентивная проверка входных параметров (так называемое оборонительное программирование). Вместо того чтобы пытаться поймать ошибку постфактум, мы должны гарантировать валидность передаваемых данных до вызова конструктора.
Рассмотрим правильный, безопасный способ инициализации объекта HTTPЗапрос:
&НаСервере
Функция СоздатьБезопасныйHTTPЗапрос(Знач АдресРесурса) Экспорт
// Шаг 1. Проверяем заполненность параметра
Если Не ЗначениеЗаполнено(АдресРесурса) Тогда
ЗаписатьВЖурналРегистрации("СетевойОбмен", УровеньЖурналаРегистрации.Предупреждение, , ,
"Попытка создания HTTP-запроса с пустым адресом ресурса.");
Возврат Неопределено;
КонецЕсли;
// Шаг 2. Строго валидируем тип данных параметра
Если ТипЗнч(АдресРесурса) <> Тип("Строка") Тогда
ЗаписатьВЖурналРегистрации("СетевойОбмен", УровеньЖурналаРегистрации.Ошибка, , ,
"Некорректный тип параметра для адреса запроса. Ожидается Строка, передано: " + ТипЗнч(АдресРесурса));
Возврат Неопределено;
КонецЕсли;
// Шаг 3. Безопасно инициализируем объект после прохождения всех проверок
Попытка
Запрос = Новый HTTPЗапрос(АдресРесурса);
Возврат Запрос;
Исключение
// Здесь мы ловим стандартные ошибки, например, некорректный формат URI
ЗаписатьВЖурналРегистрации("СетевойОбмен", УровеньЖурналаРегистрации.Ошибка, , ,
"Ошибка при инициализации HTTPЗапрос: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
Возврат Неопределено;
КонецПопытки;
КонецФункции
Разработчикам на платформе 1С важно помнить, что Попытка ... Исключение не является универсальной панацеей от любых ошибок времени выполнения. Проанализировав поведение платформы, мы можем сформулировать следующие рекомендации для повседневной разработки:
HTTPЗапрос, HTTPСоединение, Файл и др.).