Имитация и внедрение сбоев
Наблюдение за трафиком отвечает на вопрос, что приложение делает. Эта страница — о другой половине: заставить сеть ответить то, чего она никогда бы не ответила, и посмотреть, как приложение с этим справится. Бэкенд, которого ещё нет; эндпоинт, отдающий 500; ответ, приходящий через восемь секунд; API, который попросту исчез.
Всё описанное работает на любой платформе, где работает Chute, а всё, что касается HTTPS, требует сначала включить расшифровку для этого хоста: на зашифрованный запрос, который Chute не может прочитать, он не может и ответить вместо сервера.
Что Chute может имитировать, а что нет
Chute вмешивается на уровне соединения и HTTP-сообщения. Формирователя трафика у него нет, поэтому:
| Может | Вернуть заготовленное тело, вернуть выбранный код состояния, добавить фиксированную задержку перед запросом или ответом, наотрез отказать в соединении, вернуть клиентов с HTTP/3 на TCP, отправить запрос не тому хосту, который был запрошен |
| Не может | Ограничить полосу, терять или переставлять пакеты, добавлять джиттер, ухудшать соединение по ходу дела или воспроизводить конкретный RTT на транспортном уровне |
Раздела [Throttle] в конфигурации нет, ограничения скорости нет нигде. Если нужен медленный канал, а не медленный ответ, это дело сетевого кондиционера (Network Link Conditioner от Apple или маршрутизатор), а не Chute.
Выбрать механизм
| Что имитировать | Чем | Где описано |
|---|---|---|
| Тело ответа, которого ещё нет | [Map Local] |
Имитация ответов |
| Ровно 503 | [URL Rewrite] … reject |
Перезапись URL |
| Пустой 200, пустая картинка, пустой объект или массив JSON | reject-200, reject-img / reject-tinygif, reject-dict, reject-array |
Перезапись URL |
| Любой другой статус — 401, 429, 500 | [Map Local] с status-code= или скрипт http-request |
Имитация ответов, JS-скриптинг |
| Задержка | Скрипт http-request или http-response |
JS-скриптинг |
| Эндпоинт, до которого просто не достучаться | Правило REJECT |
Встроенные политики |
| Запрос, который висит, пока клиент не сдастся | Правило REJECT-DROP |
Встроенные политики |
| Клиент, который не откатывается на TCP | block-quic |
Прочие параметры |
| Другой бэкенд за тем же URL | [Host] или режим header у [URL Rewrite] |
Локальное сопоставление DNS |
Заготовленное тело ответа
[Map Local] отвечает на подходящий запрос из файла или из встроенных данных. Для расшифрованного потока HTTP/2 настоящий сервер вообще не спрашивается; в HTTP/1.1 запрос всё равно уходит, а заменяется только ответ:
[Map Local]
^https://api\.example\.com/v1/profile.* data="/Users/me/mocks/profile.json"
^https://api\.example\.com/v1/flags.* base64="eyJiZXRhIjogdHJ1ZX0="
Сработает это или нет, решают три вещи:
- Регулярное выражение совпадает в любом месте URL, как в перезаписи URL:
^https://api\.example\.com/v1/profileуже охватывает путь и любую строку запроса после него, завершающая.*не нужна. data=читает то устройство, на котором работает Chute. На Mac это удобно: правите файл — и следующий запрос видит изменение. На телефоне или Apple TV путь с вашего Mac не значит ничего; там используйтеbase64=либо отдавайте файл по HTTP и применяйте перезапись URL.- Статус —
200 OK, если не заданstatus-code=, а после ответа соединение закрывается. Заголовки ответа пишутся вheader="Key: value|…".
Тело поддерживает шаблонные переменные {{ "{{url}}" }}, {{ "{{host}}" }}, {{ "{{path}}" }}, {{ "{{method}}" }} и {{ "{{ua}}" }}, чего достаточно, чтобы сделать имитацию, отражающую то, о чём её спросили.
Код ошибки
Для 503 скрипт не нужен: перезапись URL в режиме reject возвращает HTTP/1.1 503:
[URL Rewrite]
^https://api\.example\.com/v1/orders.* _ reject
Соседние режимы покрывают остальные формы «ничего полезного»: reject-200 (200 с пустым телом), reject-img и reject-tinygif (GIF 1×1), reject-dict ({} как JSON, 200), reject-array ([] как JSON, 200). Все они применяются к HTTPS только тогда, когда этот хост расшифровывается.
Для любого другого кода состояния короче всего правило имитации ответов с status-code=:
[Map Local]
^https://api\.example\.com/v1/orders data-type=text data="rate limited" status-code=429 header="Retry-After: 30"
Если ответ должен зависеть от запроса, вместо этого скрипт http-request замыкает запрос на себя:
[Script]
Fail429 = type=http-request, script-path=/Users/me/mocks/fail429.js, pattern=^https://api\.example\.com/v1/orders
// fail429.js — ответить, не обращаясь к серверу
$done({
response: {
status: 429,
headers: {
"Content-Type": "application/json",
"Retry-After": "30"
},
body: JSON.stringify({ error: "rate_limited" })
}
})
pattern скрипта совпадает в любом месте URL, как и у семейств перезаписи: префикса вроде ^https://api\.example\.com/v1/orders достаточно, завершающая .* не нужна.
В HTTP/1.1 строка состояния несёт стандартную поясняющую фразу кода (
HTTP/1.1 429 Too Many Requests); код, которому реестр не дал названия, например 599, остаётся без неё (HTTP/1.1 599).
Задержка
Скрипт, который завёл таймер, живёт, пока не вызовет $done(), поэтому таймер — это и есть задержка. (Скрипт, который ничего не заводит, ничего и не ждёт: он пропускает сообщение сразу, как только вернёт управление.)
[Script]
SlowAPI = type=http-response, script-path=/Users/me/mocks/slow.js, pattern=^https://api\.example\.com/v1/, timeout=15
// slow.js — вернуть настоящий ответ на восемь секунд позже
setTimeout(function () {
$done({})
}, 8)
Бюджет — это собственный timeout скрипта: по умолчанию 5 секунд, всё свыше 30 урезается до 30. Скрипт, не вызвавший $done() к истечению тайм-аута, считается пропущенным без изменений (passthrough) — сообщение идёт дальше без изменений, — так что задержка длиннее тайм-аута не падает с грохотом, она просто перестаёт задерживать. Ставьте timeout больше нужной задержки, как в примере.
type=http-request задерживает до обращения к серверу (приложение видит медленный оборот), type=http-response — после (сервер был быстр, а приложение всё равно ждёт).
Эндпоинт, которого просто нет
Имитация подменяет ответ, а правило REJECT отказывает в соединении. Оно работает на уровне соединения, поэтому охватывает любой протокол, а не только HTTP, и расшифровки не требует:
[Rule]
DOMAIN-SUFFIX,api.example.com,REJECT
Разновидности REJECT отвечают по-разному, и здесь это тоже полезно. REJECT-DROP не отвечает вовсе, и клиент ждёт до тайм-аута — так выглядит мёртвый сервер. REJECT-200, REJECT-DICT, REJECT-ARRAY, REJECT-TINYGIF / REJECT-IMG и REJECT-VIDEO отвечают на открытый или расшифрованный запрос HTTP/1 синтетическим 200 (пустым, {}, [], GIF 1×1, пустым MP4). REJECT, REJECT-TINYGIF и REJECT-IMG на 30 секунд превращаются в REJECT-DROP, если один хост отклонён больше 50 раз за 30 секунд; REJECT-NO-DROP и остальные разновидности — никогда. См. Встроенные политики. Для HTTP-запросов show-error-page-for-reject = true заменяет сухой отказ читаемой страницей ошибки, и в браузере сразу видно, что блокировка ваша.
Так же проверяют, есть ли вообще запасной путь: отклоните основной хост и посмотрите, потянется ли приложение ко второму или просто зависнет.
Снять клиента с HTTP/3
QUIC работает поверх UDP, и Chute его не расшифровывает, поэтому приложение на HTTP/3 невидимо для всех механизмов этой страницы. Отклонение его QUIC-потоков заставляет совместимые клиенты повторить попытку по TCP, где всё это работает:
[General]
block-quic = on
auto отклоняет QUIC, только когда поток идёт на прокси; on отклоняет везде, включая DIRECT. Для трафика, приходящего через TUN, Chute отвечает на отклонённый QUIC-поток сообщением ICMP Port Unreachable, чтобы клиент откатился сразу, а не ждал тайм-аута.
Отправить запрос в другое место
Два способа на двух уровнях:
[Host]
api.example.com = 10.0.0.5
Сопоставление [Host] отвечает на DNS-запрос выбранным вами адресом — стенда или адреса, ведущего в никуда, если нужно соединение, уходящее в тайм-аут, а не отвергнутое. Оно действует для любого протокола и не требует расшифровки. После изменения очистите кэш DNS.
[URL Rewrite]
^https://api\.example\.com/v1/(.*) https://staging.example.com/v1/$1 header
Режим header переписывает запрос на месте и поправляет заголовок Host. Это уровень HTTP, и для HTTPS нужна расшифровка. Клиент не узнает о перезаписи, пока запрос остаётся на своём хосте. Расшифрованный HTTPS-запрос привязан к хосту, которому он был отправлен, поэтому на запрос, для которого правило называет другой хост, — как в примере выше — Chute отвечает 307 на новый URL, и клиент переходит по нему сам; так же Chute отвечает и везде, где ещё назначение нельзя переписать на месте.
Убедиться, что оно сработало
Правило, которое ни разу не совпало, выглядит ровно так же, как правило, которое совпало и ничего не сделало, — и это главный способ, которым всё описанное здесь может подвести.
- Перезаписи и имитации: страница Правила в Веб-консоли перечисляет каждое правило перезаписи URL, перезаписи заголовков, перезаписи тела и имитации ответов, сработавшее за этот запуск, со счётчиком. Нет в списке — значит ни разу не совпало. Те же данные лежат в
rewrite_hitsуGET /api/rules. - По соединению: откройте соединение в консоли и прочтите строки Применённые перезаписи, где правило названо его собственными словами. В Dashboard вкладка Общее соединения показывает их число как Сработало в разделе Правила и называет их в списке Сработавшие правила.
- Скриптов в этой таблице нет. Свидетельство скрипта — его собственный вывод: строки
console.logпопадают в журнал и читаются на странице Журналы консоли или черезGET /api/logs. Они записываются на уровне verbose, поэтому сначала поднимитеloglevelдоverbose—PUT /api/loglevelделает это без перезагрузки;console.warnиconsole.errorвидны на уровне по умолчанию. Каждая строка обрезается до 256 символов, а учётные данные в ней маскируются.
Убрать за собой
Правила, добавленные из консоли, из Dashboard или через POST /api/rewrites/:family, живут в работающем движке и исчезают при следующем перезапуске — идеально для опыта и очень плохо для того, на что вы полагаетесь. Правила в файле конфигурации переживают перезапуски, что делает файл хорошим местом для имитации — и очень опасным, если о ней забыть: строка [Map Local], оставленная в конфигурации, будет отвечать на запросы и через несколько недель, а выглядеть это будет в точности как сломанный сервер.
Эта страница — перевод английской версии. При расхождениях приоритет имеет английская версия.