Keyword check: проверка ключевого слова в HTML-ответе

4 мин чтения
Обновлено 2 мая 2026

Keyword check — это дополнительная проверка для HTTP-мониторов: помимо стандартного контроля статус-кода Tracker.ru ищет в теле ответа заданную подстроку и считает URL рабочим только тогда, когда подстрока найдена. Если страница вернула 200 OK, но ожидаемое слово исчезло — URL переходит в down и срабатывают уведомления.

Это закрывает целый класс инцидентов, при которых сервер технически жив, а контент сломан: пустой шаблон, белая страница после деплоя, страница-заглушка от reverse-proxy, дефейс или неработающая фича-флаговая панель. Без keyword-check такие случаи проходят мимо HTTP-мониторинга молча.

Когда нужен keyword check

  • Anti-deface. Подстрока — фрагмент уникального текста, который должен быть на сайте всегда (например, копирайт в подвале). Если злоумышленник заменил главную страницу — keyword не найден, алерт уходит.
  • SLO для feature-flag. Страница содержит маркер вида Feature: enabled. Когда фича-флаг сломался и страница возвращает 200, но без маркера — мониторинг ловит регрессию.
  • Контроль шаблона при 200. Кеш отдал устаревший HTML, фронт сломан, но статус всё равно 200 — keyword из ожидаемого блока (имя пользователя, нав-меню, конкретный заголовок) сразу скажет, что что-то не так.
  • Статус-страница. На публичной status-page обычно есть фраза вроде «All systems operational». Keyword-check по этой фразе превращает её в монитор «всё ли в порядке».
  • Маркер версии деплоя. Footer или meta-тег с номером версии (v1.42.0). Если deploy проехал мимо — версия не обновилась — алерт.

Как настроить

В форме редактирования URL заполните поле «Ожидаемое ключевое слово» (expected_keyword). Можно ввести любую подстроку до 255 символов: фразу, фрагмент HTML-разметки, текст из футера, имя класса CSS — что угодно, что точно встречается на ожидаемой странице.

Через API:

curl -X POST https://tracker.ru/api/v1/urls \
  -H "Authorization: Bearer <api_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/status",
    "monitor_type": "http",
    "period": 60,
    "expected_keyword": "All systems operational"
  }'

Лимит длины — 255 символов (max:255 в StoreUrlRequest). Этого достаточно для уникального фрагмента; не пытайтесь засунуть туда весь HTML страницы — храните минимально-достаточную подстроку, которую ваш фронтенд гарантирует выводить на работающей версии.

Логика срабатывания

Каждая HTTP-проверка с заданным expected_keyword отрабатывает по схеме:

  1. Чекер делает HTTP-запрос.
  2. Получает ответ, читает тело (с учётом ограничений ниже).
  3. Ищет подстроку в теле — case-insensitive substring match.
  4. Если подстрока не найдена — взводит флаг keyword_not_found = true, ставит результат Ok = false, ошибка: Expected keyword not found: <keyword>. URL переходит в down.
  5. Когда подстрока появляется обратно — keyword_not_found сбрасывается, URL восстанавливается, отправляется уведомление о восстановлении.

В таблице check_log сохраняется флаг keyword_not_found для каждой проверки — это позволяет в журнале URL отличать «упало по статусу» от «упало по keyword».

Технические нюансы

  • Auto-switch HEAD → GET. По умолчанию Tracker.ru использует HTTP-метод HEAD — он быстрее и дешевле для проверяемого сайта. Но HEAD-ответ не содержит body, и keyword там искать нечего. Поэтому если задан expected_keyword, чекер автоматически переключает метод на GET (http_checker.go:108-110). Никаких настроек на стороне пользователя для этого не нужно. Если в форме явно выбран HEAD — он будет проигнорирован и заменён на GET для этого URL.
  • Case-insensitive. Поиск нечувствителен к регистру. Обе стороны — keyword и тело ответа — приводятся к нижнему регистру через strings.ToLower (http_checker.go:168). То есть Operational, operational, OPERATIONAL — это одно и то же. Удобно: не ломается от случайной правки шаблона, в которой поменяли регистр заголовка.
  • Plain substring, не regex. Значение поля — обычная строка. Никаких метасимволов, групп, якорей. Ищется буквальное вхождение подстроки. Если нужен regex — keyword-check вам не подходит, посмотрите в сторону кастомных webhook-чекеров или health-эндпоинта.
  • Лимит body 1 MB. Чекер читает тело через io.LimitReader(resp.Body, 1<<20) — то есть только первый мегабайт ответа (http_checker.go:164). Поиск keyword идёт только в этом мегабайте. Если ваш HTML больше 1 MB — поместите ожидаемое слово ближе к началу страницы (в <head> или сразу после <body>), иначе оно окажется за лимитом и URL будет постоянно отдавать ложный keyword_not_found алерт. На практике 1 MB — это очень много для одной страницы, проблема возникает редко.
  • UTF-8. Кириллица, китайские иероглифы, эмодзи в keyword поддерживаются — оба сравниваемых байтовых потока обрабатываются как строки в UTF-8. Laravel-валидатор max:255 использует mb_strlen — лимит измеряется в символах (включая многобайтовые), а не в байтах. Для большинства практических сценариев 255 символов достаточно.
  • Совместимость с TCP-мониторингом. Поле expected_keyword доступно только для monitor_type=http. Для TCP-чеков оно игнорируется — у TCP нет body, искать негде. Подробнее в /docs/features/tcp-monitoring.

Уведомления

Когда keyword пропадает, в выбранные каналы уходит уведомление с явным указанием причины — оно отличается от обычного «status 5xx» алерта:

Сайт недоступен! https://example.com/status
Ошибка: Ключевое слово не найдено
Ожидалось: "All systems operational"
Недоступен с 2026-05-01 19:15:09

В webhook-payload поле error_status принимает значение keyword_not_found — это удобно для разделения incident-флоу: keyword-падения часто требуют другого типа реакции, чем 5xx (например, дёрнуть фронтенд-команду, а не SRE).

При восстановлении (keyword вернулся в ответ) приходит обычное сообщение о recovery с указанием длительности downtime.

Связанные статьи