Битая ссылка своими руками: проверяем, как код 1С переживает сбои
Содержание
Битую ссылку в 1С создают двумя способами. Ссылку на несуществующий объект базы даёт одна строка кода: Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор). Нерабочий адрес для HTTP-обмена получают через зарезервированный домен вроде api.nonexistent.invalid, запись в файле hosts или локальную заглушку, которая отвечает кодом 500 и тянет время.
Оба приёма нужны для одного: до выхода в рабочую базу проверить, что обработка, обмен или интеграция не падают с невнятным исключением, а пишут понятную ошибку, повторяют запрос и не портят данные. Тестировать нужно на копии базы, не на рабочей.
Две разные «битые ссылки» и зачем их моделировать
В разговоре 1С-ников под битой ссылкой понимают одно из двух.
- 🔗 Ссылка на удалённый объект. В реквизите документа лежит ссылка, а самого элемента справочника в базе уже нет. На форме вместо названия видно
<Объект не найден>и в скобках номер таблицы с идентификатором. - 🌐 Нерабочий адрес внешнего сервиса. Код обращается по HTTP к сайту, банку, маркетплейсу или другой базе, а в ответ получает тишину, отказ соединения или ошибку сервера.
Что проверяет моделирование сбоя:
- ✅ код не обрывается на первой же ошибке и не оставляет транзакцию висеть;
- ✅ пользователь видит причину, а не «Ошибка при вызове метода контекста»;
- ✅ в журнал регистрации попадает текст ответа и код состояния;
- ✅ при повторе обмен не создаёт дублей;
- ✅ фоновое задание не зависает на час из-за отсутствия таймаута.
Ждать, пока сервис упадёт сам, бессмысленно: это случится в отчётный день. Надёжнее вызвать каждый сбой нарочно и посмотреть на поведение кода.
Ссылка на несуществующий объект: как получить её кодом
Платформа позволяет получить ссылку по любому уникальному идентификатору, даже если объекта с таким идентификатором нет. Ссылка будет непустой, но «висящей в воздухе».
// Выполнять на сервере, в копии базы
БитаяСсылка = Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор);
Сообщить(БитаяСсылка.Пустая()); // Ложь
Сообщить(БитаяСсылка.ПолучитьОбъект() = Неопределено); // Истина
Две строки вывода показывают главную ловушку. Проверка Пустая() говорит, что ссылка заполнена, а объекта за ней нет. Код, который полагается только на ЗначениеЗаполнено(), пропустит такую ссылку и упадёт дальше, когда полезет в её реквизиты.
Как вписать битую ссылку в документ
Платформа не проверяет существование объекта при записи ссылки в реквизит. Поэтому битую ссылку можно положить в любой документ тестовой базы:
- Откройте документ кодом через
ПолучитьОбъект(). - Присвойте реквизиту, например
Контрагент, ссылку, полученную черезПолучитьСсылку. - Запишите объект и откройте документ в интерфейсе: в поле появится
<Объект не найден>.
Так вы увидите, как ведут себя печатная форма, проведение, отчёты и обмен, если в данных оказался «мусор».
Как проверить, что объект за ссылкой существует
Надёжная проверка делается запросом:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Т.Ссылка
|ИЗ
| Справочник.Номенклатура КАК Т
|ГДЕ
| Т.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", ПроверяемаяСсылка);
ОбъектСуществует = НЕ Запрос.Выполнить().Пустой();
В конфигурациях на БСП для этого есть готовая функция ОбщегоНазначения.СсылкаСуществует(). Если она доступна, используйте её, а не пишите свою.
Битые ссылки в рабочей базе ищет и исправляет Конфигуратор: Администрирование → Тестирование и исправление, флажок проверки ссылочной целостности. Для найденных ссылок можно выбрать: создавать объекты, очищать ссылки или ничего не менять. Процедуре нужен монопольный доступ, поэтому всех пользователей придётся отключить.
Зарезервированные домены и адреса, которые гарантированно не работают
Для HTTP-сбоев не придумывайте адрес наугад. Случайный «несуществующий» домен завтра кто-то зарегистрирует, и ваш тест начнёт ходить на чужой сервер. Стандарты интернета выделили имена и адреса, которые никогда не станут реальными.
| Что взять | Пример | Какой сбой получится |
|---|---|---|
Домен .invalid | api.nonexistent.invalid | Имя не разрешается в адрес (ошибка DNS) |
Домен .test | shop.test | То же, если нет записи в hosts |
| Адрес из тестовых сетей | 192.0.2.1, 203.0.113.10 | Пакеты уходят в никуда, соединение висит до таймаута |
| Закрытый порт на своей машине | 127.0.0.1:65000 | Мгновенный отказ соединения |
Домены example.com, example.org | example.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 с отбором по исключениям. Он пишет много данных, поэтому включайте его на короткое время и ограничивайте размер и срок хранения.
Частые вопросы по тестированию сбоев в 1С
Можно ли создать битую ссылку без программирования?
ПолучитьСсылку во внешней обработке или консоли кода. Ещё битые ссылки появляются сами, если загрузить в тестовую базу документы из обмена без связанных справочников.Почему битая ссылка не ловится проверкой ЗначениеЗаполнено()?
ОбщегоНазначения.СсылкаСуществует() в конфигурациях на БСП.Зачем отдельный домен .invalid, если можно написать любой выдуманный адрес?
.invalid зарезервирован стандартом и не разрешается никогда.