モックと障害注入
トラフィックを眺めれば、アプリが何をしているかは分かります。このページはもう半分の話です。ネットワークに、本来返さないはずの答えを返させて、アプリがそれにどう反応するかを見る。まだ作られていないバックエンド、500 を返すエンドポイント、8 秒かかるレスポンス、そっくり消えてしまった API。
ここに書かれたことは Chute が動くすべてのプラットフォームで成り立ちます。HTTPS に触れるものはすべて、そのホストに対して先に復号を有効にしておく必要があります — Chute が読めない暗号化リクエストには、Chute も答えようがありません。
Chute にできること・できないこと
Chute が介入するのは接続と HTTP メッセージです。トラフィックシェーパーは持っていません:
| できる | 固定のボディを返す、指定のステータスコードを返す、リクエストやレスポンスの前に固定の遅延を入れる、接続をきっぱり拒否する、HTTP/3 のクライアントを TCP に戻させる、リクエストを本来とは別のホストへ送る |
| できない | 帯域を制限する、パケットを落とす・並べ替える、ジッタを加える、接続を途中から劣化させる、トランスポート層で特定の RTT を再現する |
設定に [Throttle] セクションはありませんし、速度制限もどこにもありません。遅いレスポンスではなく遅いリンクが必要なら、それは Chute ではなくネットワークコンディショナー(Apple の Network Link Conditioner、またはルーター)の仕事です。
仕組みを選ぶ
| 再現したいもの | 使うもの | 参照先 |
|---|---|---|
| まだ存在しないレスポンスボディ | [Map Local] |
モックレスポンス |
| ちょうど 503 | [URL Rewrite] … reject |
URL 書き換え |
| 空の 200、空白画像、空の JSON オブジェクト | reject-200、reject-img、reject-dict |
URL 書き換え |
| それ以外のステータス — 401、429、500 | http-request スクリプト |
JS スクリプト |
| 遅延 | http-request または http-response スクリプト |
JS スクリプト |
| そもそも到達できないエンドポイント | REJECT ルール |
組み込みポリシー |
| TCP にフォールバックしないクライアント | block-quic |
その他のオプション |
| 同じ URL の裏側にある別のバックエンド | [Host]、または [URL Rewrite] の header モード |
ローカル DNS マッピング |
固定のレスポンスボディ
[Map Local] は、実際のサーバーに尋ねずに、ファイルまたはインラインの base64 でマッチしたリクエストに応答します:
[Map Local]
^https://api\.example\.com/v1/profile.* data="/Users/me/mocks/profile.json"
^https://api\.example\.com/v1/flags.* base64="eyJiZXRhIjogdHJ1ZX0="
これが効くかどうかは 3 点で決まります:
- 正規表現は URL 全体にマッチしなければなりません。一部ではありません。クエリ文字列がまったく無い URL だけを狙うのでなければ、パターンは
.*で終わらせてください。 data=は Chute を動かしているデバイスが読みます。 Mac では便利です — ファイルを直せば次のリクエストから反映されます。スマートフォンや Apple TV では、あなたの Mac 上のパスには何の意味もありません。そこではbase64=を使うか、ファイルを HTTP で配信して URL 書き換えを使ってください。- ステータスは常に
200 OKです。[Map Local]にはステータスを設定する手段が無く、応答後に接続は閉じられます。それ以外のステータスにはスクリプトを使ってください — 次の節を参照。
ボディは {{ "{{url}}" }}、{{ "{{host}}" }}、{{ "{{path}}" }}、{{ "{{method}}" }}、{{ "{{ua}}" }} のテンプレート変数に対応しており、問い合わせ内容をそのまま返すモックを作るには十分です。
エラーステータス
503 ならスクリプトは不要です。reject モードの URL 書き換えが HTTP/1.1 503 を返します:
[URL Rewrite]
^https://api\.example\.com/v1/orders.* _ reject
同じ仲間が「使える中身が無い」の他のかたちをカバーします:reject-200(空ボディの 200)、reject-img(1×1 の GIF)、reject-dict(JSON の {}、200)。いずれも HTTPS に対しては、そのホストが復号されているときにだけ効きます。
それ以外の任意のステータスコードには、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 の経路では、ステータスコードが何であれステータス行のリーズンフレーズは
OKと書かれます(HTTP/1.1 429 OK)。クライアントが読むのは数値でフレーズではないので見た目だけの問題ですが、生のキャプチャではそう見えます。
遅延
スクリプトは $done() を呼ぶまでメッセージを止めるので、タイマーがそのまま遅延になります:
[Script]
SlowAPI = type=http-response, script-path=/Users/me/mocks/slow.js, pattern=^https://api\.example\.com/v1/, timeout=15
// slow.js — 本物のレスポンスを 8 秒遅れて返す
setTimeout(function () {
$done({})
}, 8)
予算はスクリプト自身の timeout です:既定は 5 秒、30 を超える値は 30 に丸められます。タイムアウト時に $done() を呼んでいないスクリプトはパススルーとして扱われ — メッセージはそのまま進みます — なので、タイムアウトより長い遅延は派手に失敗するのではなく、単に遅延しなくなります。上の例のように、timeout は欲しい遅延より大きく設定してください。
サーバーに接続する前に遅らせるなら type=http-request(アプリからは往復が遅く見えます)、あとで遅らせるなら type=http-response(サーバーは速かったのにアプリは待たされます)を使います。
そっくり消えたエンドポイント
モックはレスポンスを差し替えますが、REJECT ルールは接続を拒みます。接続のレベルで働くので HTTP だけでなく任意のプロトコルを覆い、復号も不要です:
[Rule]
DOMAIN-SUFFIX,api.example.com,REJECT
REJECT-DROP、REJECT-TINYGIF、REJECT-NO-DROP は互換のために受け付けられ、いずれも通常の REJECT として振る舞います。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 ポート到達不能で応答するので、クライアントはタイムアウトを待たずにすぐフォールバックします。
リクエストを別の場所へ送る
2 つのレイヤーに 2 つの方法があります:
[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 には復号が必要です。宛先をその場で書き換えられない場合、Chute は新しい URL への 307 で応答するようフォールバックします。
本当に効いたかを確かめる
一度もマッチしなかったルールは、マッチしたけれど何もしなかったルールとまったく同じに見えます — このページ全体の失敗のしかたがこれです。
- 書き換えとモック:Web コンソールのルールページが、この実行で効いた URL 書き換え・Header 書き換え・Body 書き換え・Map Local のルールを回数付きで一覧します。そこに無いルールは一度もマッチしていません。同じデータは
GET /api/rulesのrewrite_hitsにあります。 - 接続ごと:コンソールか Dashboard でその接続を開き、適用された書き換えの行を読んでください。ルール自身の言葉でルールを示します。
- スクリプトはその表に出ません。 スクリプトの証拠はスクリプト自身の出力です。
console.logの行はログに残り、コンソールのログページやGET /api/logsで読めます。
後片付け
コンソール、Dashboard、あるいは POST /api/rewrites/:family から追加したルールは動作中のカーネルの中にあり、次の再起動で消えます — 実験には理想的ですが、頼りにするものを置く場所としては最悪です。設定ファイルの中のルールは再起動を越えて残るので、モックを置くにはよい場所であり、同時に忘れてはならないものでもあります。設定に残された [Map Local] の 1 行は数週間後もリクエストに答え続け、しかも壊れたサーバーとまったく同じに見えます。
本ページは英語版からの翻訳です。内容に相違がある場合は、英語版が優先されます。