Битая ссылка своими руками: проверяем, как код 1С переживает сбои

· 10 мин

Содержание

Битую ссылку в 1С создают двумя способами. Ссылку на несуществующий объект базы даёт одна строка кода: Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор). Нерабочий адрес для HTTP-обмена получают через зарезервированный домен вроде api.nonexistent.invalid, запись в файле hosts или локальную заглушку, которая отвечает кодом 500 и тянет время.

Оба приёма нужны для одного: до выхода в рабочую базу проверить, что обработка, обмен или интеграция не падают с невнятным исключением, а пишут понятную ошибку, повторяют запрос и не портят данные. Тестировать нужно на копии базы, не на рабочей.

Две разные «битые ссылки» и зачем их моделировать

В разговоре 1С-ников под битой ссылкой понимают одно из двух.

  • 🔗 Ссылка на удалённый объект. В реквизите документа лежит ссылка, а самого элемента справочника в базе уже нет. На форме вместо названия видно <Объект не найден> и в скобках номер таблицы с идентификатором.
  • 🌐 Нерабочий адрес внешнего сервиса. Код обращается по HTTP к сайту, банку, маркетплейсу или другой базе, а в ответ получает тишину, отказ соединения или ошибку сервера.

Что проверяет моделирование сбоя:

  • ✅ код не обрывается на первой же ошибке и не оставляет транзакцию висеть;
  • ✅ пользователь видит причину, а не «Ошибка при вызове метода контекста»;
  • ✅ в журнал регистрации попадает текст ответа и код состояния;
  • ✅ при повторе обмен не создаёт дублей;
  • ✅ фоновое задание не зависает на час из-за отсутствия таймаута.

Ждать, пока сервис упадёт сам, бессмысленно: это случится в отчётный день. Надёжнее вызвать каждый сбой нарочно и посмотреть на поведение кода.

Ссылка на несуществующий объект: как получить её кодом

Платформа позволяет получить ссылку по любому уникальному идентификатору, даже если объекта с таким идентификатором нет. Ссылка будет непустой, но «висящей в воздухе».

// Выполнять на сервере, в копии базы
БитаяСсылка = Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор);

Сообщить(БитаяСсылка.Пустая());                       // Ложь
Сообщить(БитаяСсылка.ПолучитьОбъект() = Неопределено); // Истина

Две строки вывода показывают главную ловушку. Проверка Пустая() говорит, что ссылка заполнена, а объекта за ней нет. Код, который полагается только на ЗначениеЗаполнено(), пропустит такую ссылку и упадёт дальше, когда полезет в её реквизиты.

Как вписать битую ссылку в документ

Платформа не проверяет существование объекта при записи ссылки в реквизит. Поэтому битую ссылку можно положить в любой документ тестовой базы:

  1. Откройте документ кодом через ПолучитьОбъект().
  2. Присвойте реквизиту, например Контрагент, ссылку, полученную через ПолучитьСсылку.
  3. Запишите объект и откройте документ в интерфейсе: в поле появится <Объект не найден>.

Так вы увидите, как ведут себя печатная форма, проведение, отчёты и обмен, если в данных оказался «мусор».

Как проверить, что объект за ссылкой существует

Надёжная проверка делается запросом:

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
|	Т.Ссылка
|ИЗ
|	Справочник.Номенклатура КАК Т
|ГДЕ
|	Т.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", ПроверяемаяСсылка);
ОбъектСуществует = НЕ Запрос.Выполнить().Пустой();

В конфигурациях на БСП для этого есть готовая функция ОбщегоНазначения.СсылкаСуществует(). Если она доступна, используйте её, а не пишите свою.

Битые ссылки в рабочей базе ищет и исправляет Конфигуратор: Администрирование → Тестирование и исправление, флажок проверки ссылочной целостности. Для найденных ссылок можно выбрать: создавать объекты, очищать ссылки или ничего не менять. Процедуре нужен монопольный доступ, поэтому всех пользователей придётся отключить.

Зарезервированные домены и адреса, которые гарантированно не работают

Для HTTP-сбоев не придумывайте адрес наугад. Случайный «несуществующий» домен завтра кто-то зарегистрирует, и ваш тест начнёт ходить на чужой сервер. Стандарты интернета выделили имена и адреса, которые никогда не станут реальными.

Что взятьПримерКакой сбой получится
Домен .invalidapi.nonexistent.invalidИмя не разрешается в адрес (ошибка DNS)
Домен .testshop.testТо же, если нет записи в hosts
Адрес из тестовых сетей192.0.2.1, 203.0.113.10Пакеты уходят в никуда, соединение висит до таймаута
Закрытый порт на своей машине127.0.0.1:65000Мгновенный отказ соединения
Домены example.com, example.orgexample.com/apiСайт отвечает, но нужного метода нет, обычно код 404

Получается готовый набор: одна строка в настройках обмена, и вы последовательно проверяете ошибку имени, зависание и отказ.

// Подставьте вместо адреса сервиса по очереди:
// "api.nonexistent.invalid" — ошибка разрешения имени
// "192.0.2.1"               — ожидание до таймаута
// "127.0.0.1", порт 65000   — отказ соединения
Соединение = Новый HTTPСоединение("api.nonexistent.invalid", 443, , , , 10, Новый ЗащищенноеСоединениеOpenSSL);

Текст исключения в 8.3 обычно содержит английскую формулировку сетевой библиотеки, например Couldn't resolve host name. Не стройте логику на разборе этой строки: она может меняться от версии платформы.

Подмена адреса сервиса через файл hosts

Когда адрес сервиса жёстко зашит в конфигурацию или менять настройки обмена нельзя, сбой создают на уровне системы. Файл hosts перенаправляет нужное имя на другой адрес.

Где лежит файл:

  • Windows: C:\Windows\System32\drivers\etc\hosts (редактор нужно запускать от имени администратора);
  • Linux: /etc/hosts.

Пример записей:

# Отказ соединения: на локальном порту 443 никто не слушает
127.0.0.1    api.partner-service.ru

# Перенаправление на свою заглушку в локальной сети
192.168.1.50    api.partner-service.ru

После правки в Windows сбросьте кэш DNS командой ipconfig /flushdns, иначе система ещё какое-то время будет ходить на старый адрес.

На какой машине править hosts

Это главный подводный камень. Файл нужно менять там, где реально выполняется HTTP-запрос:

Режим работыГде выполняется серверный кодГде править hosts
Файловая база, тонкий или толстый клиентНа компьютере пользователяНа этом компьютере
Клиент-серверная базаНа сервере 1С, в рабочем процессеНа сервере 1С
Публикация на веб-сервере, файловая базаНа веб-сервереНа веб-сервере

Если 1С выходит в интернет через прокси, имя разрешает прокси-сервер, и правка hosts на машине с 1С ничего не даст. Проверьте настройки прокси в параметрах соединения.

Правка hosts на сервере 1С ломает обмен для всех пользователей этого сервера. Делайте это на тестовом сервере или в согласованное окно, а после проверки удалите строку и снова сбросьте кэш.

Таймауты и обрывы соединения

Самый коварный сбой — не ошибка, а молчание. Без таймаута запрос к зависшему сервису может ждать очень долго, а вместе с ним стоит фоновое задание или форма пользователя.

Задайте таймаут явно

Шестой параметр конструктора HTTPСоединение — таймаут в секундах. Значение 0 означает отсутствие ограничения.

// Сервер, Порт, Пользователь, Пароль, Прокси, Таймаут, ЗащищенноеСоединение
Соединение = Новый HTTPСоединение("192.0.2.1", 80, , , , 5);

С адресом 192.0.2.1 вызов Получить() через 5 секунд закончится исключением. Если исключение пришло через минуту, значит, таймаут где-то не передаётся.

Заглушка, которая тянет время и рвёт ответ

Для управляемых сбоев удобна локальная заглушка на Python. Она не требует установки пакетов и запускается одной командой python mock.py:

import socket
import time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        parts = self.path.strip("/").split("/")
        if parts[0] == "status" and len(parts) > 1:
            code = int(parts[1])
            body = ('{"error": %d}' % code).encode("utf-8")
            self.send_response(code)
            self.send_header("Content-Type", "application/json; charset=utf-8")
            self.send_header("Content-Length", str(len(body)))
            self.end_headers()
            self.wfile.write(body)
        elif parts[0] == "delay" and len(parts) > 1:
            time.sleep(int(parts[1]))
            self.send_response(200)
            self.send_header("Content-Length", "2")
            self.end_headers()
            self.wfile.write(b"ok")
        elif parts[0] == "drop":
            # Обещаем 1000 байт, отдаём 10 и рвём соединение
            self.send_response(200)
            self.send_header("Content-Length", "1000")
            self.end_headers()
            self.wfile.write(b"0123456789")
            self.wfile.flush()
            self.connection.shutdown(socket.SHUT_RDWR)
        else:
            self.send_response(404)
            self.send_header("Content-Length", "0")
            self.end_headers()

ThreadingHTTPServer(("0.0.0.0", 8085), Handler).serve_forever()

Что даёт каждый адрес:

  • ⏳ /delay/30 — ответ через 30 секунд, проверка таймаута;
  • ✂️ /drop — обрыв на середине ответа;
  • 🚫 /status/500, /status/503 и любой другой код — ответ с нужной ошибкой.

Блокировка исходящих соединений брандмауэром

Если нужен сбой «сервис пропал совсем», закройте исходящие соединения к его адресу правилом брандмауэра Windows:

netsh advfirewall firewall add rule name="test-block-api" dir=out action=block remoteip=203.0.113.10
netsh advfirewall firewall delete rule name="test-block-api"

Первая команда включает блокировку, вторая снимает. Правило действует на всю машину: на сервере 1С оно отрежет сервис у всех сеансов.

Ответы 404, 500, 503: проверяем разбор кодов состояния

Здесь начинающие разработчики ошибаются чаще всего. Метод Получить() бросает исключение только при сетевой проблеме. Ответ с кодом 500 для платформы — нормальный ответ: исключения не будет, просто КодСостояния окажется равным 500.

Значит, проверка кода обязательна в каждом вызове. Пример обёртки, которая обрабатывает оба типа сбоев:

Функция ВыполнитьЗапрос(Сервер, Порт, Адрес, Таймаут = 10) Экспорт

	Результат = Новый Структура("Успех, КодСостояния, Тело, Ошибка", Ложь, 0, "", "");

	Попытка
		Соединение = Новый HTTPСоединение(Сервер, Порт, , , , Таймаут);
		Ответ = Соединение.Получить(Новый HTTPЗапрос(Адрес));
	Исключение
		Результат.Ошибка = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
		ЗаписьЖурналаРегистрации("Интеграция.HTTP", УровеньЖурналаРегистрации.Ошибка, , ,
			"Сеть: " + Результат.Ошибка);
		Возврат Результат;
	КонецПопытки;

	Результат.КодСостояния = Ответ.КодСостояния;
	Результат.Тело = Ответ.ПолучитьТелоКакСтроку();
	Результат.Успех = Ответ.КодСостояния >= 200 И Ответ.КодСостояния < 300;

	Если Не Результат.Успех Тогда
		Результат.Ошибка = СтрШаблон("Код %1: %2", Формат(Ответ.КодСостояния, "ЧГ=0"), Лев(Результат.Тело, 500));
		ЗаписьЖурналаРегистрации("Интеграция.HTTP", УровеньЖурналаРегистрации.Ошибка, , ,
			Результат.Ошибка);
	КонецЕсли;

	Возврат Результат;

КонецФункции

Прогоните обёртку по заглушке и сверьте реакцию с таблицей:

КодЧто значитКак должен реагировать код 1С
400Сервис не понял запросНе повторять, показать текст ответа: ошибка в данных
401, 403Нет доступаНе повторять, сообщить о проблеме с ключом или паролем
404Нет такого метода или объектаПроверить адрес; не повторять в цикле
429Слишком много запросовПодождать и повторить позже
500Ошибка на стороне сервисаЗаписать в журнал, повторить позже
502, 503, 504Сервис или шлюз недоступенПовторить с паузой, ограничить число попыток

Повтор допустим только для запросов, которые безопасно отправить дважды. Если сервис на 500-й ошибке успел создать заказ, слепой повтор создаст второй. Передавайте свой идентификатор операции, если сервис это поддерживает.

Диагностика: выберите симптом

Журнал регистрации и отладчик: как поймать исключение

Сбой вызван, теперь его надо увидеть. Три инструмента закрывают почти все случаи.

Журнал регистрации

Записи из обёртки выше лежат в журнале с событием Интеграция.HTTP. Открыть журнал можно в пользовательском режиме: Администрирование → Обслуживание → Журнал регистрации, если у вас БСП-конфигурация вроде Бухгалтерии 3.0 или ЗУП 3.1. В отборе укажите событие и уровень «Ошибка».

Хорошая запись в журнале отвечает на три вопроса: куда шли, что получили, чем закончилось. Пишите адрес, код состояния и первые 300-500 символов тела ответа. Пароли и токены в журнал не выводите.

Остановка по ошибке в отладчике

В Конфигураторе включите в меню Отладка режим остановки по ошибке. Тогда отладчик остановится на строке, где возникло исключение, даже если оно перехвачено в Попытка ... Исключение. Это быстрый способ найти место, где ошибку «глотают» без записи.

Для кода на сервере 1С отладка должна быть разрешена на стороне сервера. Если точки останова в серверных процедурах не срабатывают, проверьте, что сервер запущен с включённой отладкой.

Технологический журнал

Когда ошибка плавает и в журнале регистрации её не видно, включают технологический журнал через файл logcfg.xml с отбором по исключениям. Он пишет много данных, поэтому включайте его на короткое время и ограничивайте размер и срок хранения.

Сценарии, которые стоит прогнать перед выпуском интеграции
Выполнено: 0 / 7

Частые вопросы по тестированию сбоев в 1С

Можно ли создать битую ссылку без программирования?
Штатной кнопки для этого нет: интерактивное удаление помеченных объектов проверяет, есть ли на объект ссылки, и не даст удалить используемый элемент. Минимум кода — одна строка с ПолучитьСсылку во внешней обработке или консоли кода. Ещё битые ссылки появляются сами, если загрузить в тестовую базу документы из обмена без связанных справочников.
Почему битая ссылка не ловится проверкой ЗначениеЗаполнено()?
Функция проверяет только, что ссылка непустая. Существование объекта в базе она не проверяет. Для этого нужен запрос к таблице или ОбщегоНазначения.СсылкаСуществует() в конфигурациях на БСП.
Зачем отдельный домен .invalid, если можно написать любой выдуманный адрес?
Выдуманный адрес могут зарегистрировать, и тест начнёт отправлять данные на чужой сервер. Домен .invalid зарезервирован стандартом и не разрешается никогда.
Нужно ли тестировать сбои в файловой базе, если в работе клиент-сервер?
Логику кода проверить можно. Но сетевые условия отличаются: в клиент-серверном режиме запрос уходит с сервера 1С, у которого свои DNS, прокси и брандмауэр. Финальную проверку делайте в той же схеме, что и в работе.
Как проверить HTTPS-сервис, если заглушка работает по HTTP?
Для проверки обработки кодов и таймаутов хватит HTTP. Отдельно проверьте защищённое соединение на реальном тестовом стенде сервиса, особенно ошибки сертификата.