モックと障害注入
トラフィックを眺めれば、アプリが何をしているかは分かります。このページはもう半分の話です。ネットワークに、本来返さないはずの答えを返させて、アプリがそれにどう反応するかを見る。まだ作られていないバックエンド、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-tinygif、reject-dict、reject-array |
URL書き換え |
| それ以外のステータス — 401、429、500 | status-code= 付きの [Map Local]、または http-request スクリプト |
モックレスポンス、JSスクリプト |
| 遅延 | http-request または http-response スクリプト |
JSスクリプト |
| そもそも到達できないエンドポイント | REJECT ルール |
組み込みポリシー |
| クライアントが諦めるまで応答しないリクエスト | REJECT-DROP ルール |
組み込みポリシー |
| TCPにフォールバックしないクライアント | block-quic |
その他オプション |
| 同じURLの裏側にある別のバックエンド | [Host]、または [URL Rewrite] の header モード |
ローカル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="
これが効くかどうかは 3 点で決まります:
- 正規表現はURLのどこにでもマッチします。URL書き換えと同じです。
^https://api\.example\.com/v1/profileだけでそのパスと後に続くクエリ文字列を含むので、末尾の.*は要りません。 data=はChuteを動かしているデバイスが読みます。 Macでは便利です — ファイルを直せば次のリクエストから反映されます。スマートフォンやApple TVでは、あなたのMac上のパスには何の意味もありません。そこではbase64=を使うか、ファイルをHTTPで配信してURL書き換えを使ってください。- ステータスは
status-code=を指定しない限り200 OKです。 応答後に接続は閉じられます。レスポンスヘッダーはheader="Key: value|…"に書きます。
ボディは {{ "{{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 と reject-tinygif(1×1 のGIF)、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 — 本物のレスポンスを 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の派生は応答のしかたがそれぞれ異なり、ここでも役立ちます。REJECT-DROP は一切応答しないので、クライアントはタイムアウトするまで待ちます — サーバーが死んでいるときの見え方です。REJECT-200、REJECT-DICT、REJECT-ARRAY、REJECT-TINYGIF / REJECT-IMG、REJECT-VIDEO は、平文または復号済みのHTTP/1リクエストに合成した 200(空、{}、[]、1×1 のGIF、空白のMP4)を返します。同じホストが 30 秒間に 50 回を超えて拒否されると、REJECT、REJECT-TINYGIF、REJECT-IMG は以後 30 秒間 REJECT-DROP になります。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ポート到達不能で応答するので、クライアントはタイムアウトを待たずにすぐフォールバックします。
リクエストを別の場所へ送る
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には復号が必要です。リクエストのホストが変わらない限り、クライアントは書き換えに気づきません。復号されたHTTPSリクエストは送信先のホストに結び付いているため、上の例のように別のホストを指定するルールには新しいURLへの 307 で応答し、クライアントが自分でそれに従います。宛先をその場で書き換えられないその他の場合も、Chuteは同じように応答します。
本当に効いたかを確かめる
一度もマッチしなかったルールは、マッチしたけれど何もしなかったルールとまったく同じに見えます — このページ全体の失敗のしかたがこれです。
- 書き換えとモック: Webコンソールのルールページが、この実行で効いたURL書き換え・ヘッダー書き換え・ボディ書き換え・モックレスポンスのルールを回数付きで一覧します。そこに無いルールは一度もマッチしていません。同じデータは
GET /api/rulesのrewrite_hitsにあります。 - 接続ごと: コンソールでその接続を開き、適用された書き換えの行を読んでください。ルール自身の言葉でルールを示します。Dashboardでは、その接続の一般タブがルールの下の適用にその数を示し、適用されたルールの一覧に名前を挙げます。
- スクリプトはその表に出ません。 スクリプトの証拠はスクリプト自身の出力です。
console.logの行はログに残り、コンソールのログページやGET /api/logsで読めます。これらは verbose レベルで書かれるため、まずloglevelをverboseに上げてください —PUT /api/loglevelなら再読み込みなしで変更できます。console.warnとconsole.errorはデフォルトのレベルで表示されます。各行は 256 文字で切り詰められ、認証情報はマスクされます。
後片付け
コンソール、Dashboard、あるいは POST /api/rewrites/:family から追加したルールは動作中のエンジンの中にあり、次の再起動で消えます — 実験には理想的ですが、頼りにするものを置く場所としては最悪です。設定ファイルの中のルールは再起動を越えて残るので、モックを置くにはよい場所であり、同時に忘れてはならないものでもあります。設定に残された [Map Local] の 1 行は数週間後もリクエストに答え続け、しかも壊れたサーバーとまったく同じに見えます。
本ページは英語版からの翻訳です。内容に相違がある場合は、英語版が優先されます。