Simulation et injection de pannes

Regarder le trafic répond à la question de ce qu'une application fait. Cette page traite de l'autre moitié : faire répondre au réseau ce qu'il n'aurait jamais répondu, pour voir ce que l'application en fait. Un backend qui n'existe pas encore, un point de terminaison qui renvoie 500, une réponse qui met huit secondes, une API tout bonnement disparue.

Tout ce qui suit fonctionne sur chaque plateforme où Chute s'exécute, et tout ce qui touche au HTTPS exige d'abord le déchiffrement pour l'hôte concerné : une requête chiffrée que Chute ne peut pas lire est une requête à laquelle il ne peut pas répondre à la place du serveur.

Ce que Chute peut simuler, et ce qu'il ne peut pas

Chute intervient au niveau de la connexion et du message HTTP. Il n'a pas de régulateur de trafic, donc :

Peut Renvoyer un corps tout fait, renvoyer un code de statut choisi, ajouter un délai fixe avant une requête ou une réponse, refuser purement et simplement une connexion, ramener les clients HTTP/3 vers TCP, envoyer une requête à un autre hôte que celui demandé
Ne peut pas Limiter la bande passante, perdre ou réordonner des paquets, ajouter de la gigue, dégrader une connexion en cours de route, ou simuler un RTT précis au niveau transport

Il n'y a pas de section [Throttle] dans la configuration, et pas de limite de vitesse nulle part. S'il vous faut une liaison lente plutôt qu'une réponse lente, cela relève d'un conditionneur de réseau (le Network Link Conditioner d'Apple, ou un routeur), pas de Chute.

Choisir un mécanisme

Pour simuler Utilisez Où
Un corps de réponse qui n'existe pas encore [Map Local] Réponse simulée
Exactement 503 [URL Rewrite] … reject Réécriture d'URL
Un 200 vide, une image vide, un objet ou un tableau JSON vide reject-200, reject-img / reject-tinygif, reject-dict, reject-array Réécriture d'URL
Tout autre statut — 401, 429, 500 [Map Local] avec status-code=, ou un script http-request Réponse simulée, Scripts JS
De la latence Un script http-request ou http-response Scripts JS
Un point de terminaison simplement injoignable Une règle REJECT Politique intégrée
Une requête qui reste suspendue jusqu'à ce que le client abandonne Une règle REJECT-DROP Politique intégrée
Un client qui ne retombe pas sur TCP block-quic Options diverses
Un autre backend derrière la même URL [Host], ou le mode header de [URL Rewrite] Mappage DNS local

Un corps de réponse tout fait

[Map Local] répond à une requête correspondante depuis un fichier ou depuis des données en ligne. Sur un flux HTTP/2 déchiffré, le vrai serveur n'est jamais interrogé ; en HTTP/1.1, la requête part quand même et seule la réponse est remplacée :

[Map Local]
^https://api\.example\.com/v1/profile.* data="/Users/me/mocks/profile.json"
^https://api\.example\.com/v1/flags.* base64="eyJiZXRhIjogdHJ1ZX0="

Trois choses décident si cela fonctionne :

  • L'expression régulière correspond n'importe où dans l'URL, comme pour la réécriture d'URL : ^https://api\.example\.com/v1/profile couvre déjà le chemin et toute chaîne de requête qui suit, un .* final est inutile.
  • data= est lu par l'appareil qui exécute Chute. Sur un Mac, c'est commode : modifiez le fichier, et la requête suivante voit le changement. Sur un téléphone ou une Apple TV, un chemin venu de votre Mac ne signifie rien ; utilisez-y base64=, ou servez le fichier en HTTP et passez par la réécriture d'URL.
  • Le statut est 200 OK sauf si vous fixez status-code=, et la connexion est fermée après la réponse. Les en-têtes de réponse se placent dans header="Key: value|…".

Le corps prend en charge les variables de modèle {{ "{{url}}" }}, {{ "{{host}}" }}, {{ "{{path}}" }}, {{ "{{method}}" }} et {{ "{{ua}}" }}, ce qui suffit à fabriquer une simulation qui renvoie ce qu'on lui a demandé.

Un statut d'erreur

Pour 503, aucun script n'est nécessaire : une réécriture d'URL en mode reject renvoie HTTP/1.1 503 :

[URL Rewrite]
^https://api\.example\.com/v1/orders.* _ reject

Les modes voisins couvrent les autres formes de « rien d'utile » : reject-200 (200 avec un corps vide), reject-img et reject-tinygif (un GIF 1×1), reject-dict ({} en JSON, 200), reject-array ([] en JSON, 200). Tous ne s'appliquent au HTTPS que lorsque cet hôte est déchiffré.

Pour n'importe quel autre code de statut, une règle de réponse simulée avec status-code= est le plus court :

[Map Local]
^https://api\.example\.com/v1/orders data-type=text data="rate limited" status-code=429 header="Retry-After: 30"

Lorsque la réponse doit dépendre de la requête, un script http-request court-circuite plutôt la requête :

[Script]
Fail429 = type=http-request, script-path=/Users/me/mocks/fail429.js, pattern=^https://api\.example\.com/v1/orders
// fail429.js — répondre sans contacter le serveur
$done({
    response: {
        status: 429,
        headers: {
            "Content-Type": "application/json",
            "Retry-After": "30"
        },
        body: JSON.stringify({ error: "rate_limited" })
    }
})

Le pattern d'un script correspond n'importe où dans l'URL, comme les familles de réécriture : un préfixe comme ^https://api\.example\.com/v1/orders suffit, et un .* final est inutile.

En HTTP/1.1, la ligne de statut porte la phrase de raison standard du code (HTTP/1.1 429 Too Many Requests) ; un code auquel le registre ne donne pas de nom, comme 599, n'en a aucune (HTTP/1.1 599).

La latence

Un script qui enregistre un minuteur reste vivant jusqu'à ce qu'il appelle $done() : le minuteur est donc un délai. (Un script qui n'enregistre rien n'attend pas : il laisse passer dès qu'il rend la main.)

[Script]
SlowAPI = type=http-response, script-path=/Users/me/mocks/slow.js, pattern=^https://api\.example\.com/v1/, timeout=15
// slow.js — rendre la vraie réponse avec huit secondes de retard
setTimeout(function () {
    $done({})
}, 8)

Le budget, c'est le timeout du script lui-même : 5 secondes par défaut, et toute valeur au-delà de 30 est ramenée à 30. Un script qui n'a pas appelé $done() à l'expiration du délai est traité comme une transmission sans modification (passthrough) — le message poursuit sa route tel quel —, si bien qu'un délai plus long que le timeout n'échoue pas bruyamment : il cesse simplement de retarder. Réglez timeout au-dessus du délai souhaité, comme dans l'exemple.

Utilisez type=http-request pour retarder avant de contacter le serveur (l'application voit un aller-retour lent), et type=http-response pour retarder après (le serveur a été rapide, l'application attend quand même).

Un point de terminaison purement et simplement disparu

Une simulation remplace une réponse ; une règle REJECT refuse la connexion. Elle agit au niveau de la connexion, couvre donc n'importe quel protocole et pas seulement HTTP, et ne demande pas de déchiffrement :

[Rule]
DOMAIN-SUFFIX,api.example.com,REJECT

Les variantes de REJECT répondent chacune à leur manière, ce qui sert ici aussi. REJECT-DROP ne répond jamais : le client attend jusqu'à l'expiration — c'est à quoi ressemble un serveur mort. REJECT-200, REJECT-DICT, REJECT-ARRAY, REJECT-TINYGIF / REJECT-IMG et REJECT-VIDEO répondent à une requête HTTP/1 en clair ou déchiffrée par un 200 synthétique (vide, {}, [], un GIF 1×1, un MP4 vierge). REJECT, REJECT-TINYGIF et REJECT-IMG deviennent REJECT-DROP pendant 30 secondes dès qu'un hôte a été rejeté plus de 50 fois en 30 secondes ; REJECT-NO-DROP et les autres variantes, jamais. Voir Politique intégrée. Pour les requêtes HTTP, show-error-page-for-reject = true remplace le refus sec par une page d'erreur lisible, ce qui rend évident dans un navigateur que le blocage vient de vous.

C'est aussi ainsi qu'on vérifie qu'un chemin de repli existe : rejetez l'hôte principal et voyez si l'application se tourne vers le secondaire ou tourne simplement en rond.

Faire descendre un client de HTTP/3

QUIC circule sur UDP et Chute ne sait pas le déchiffrer : une application en HTTP/3 est donc invisible pour tous les mécanismes de cette page. Rejeter ses flux QUIC amène les clients compatibles à réessayer en TCP, où tout cela fonctionne :

[General]
block-quic = on

auto ne rejette QUIC que lorsque le flux se dirige vers un proxy ; on le rejette partout, DIRECT compris. Pour le trafic arrivant par TUN, Chute répond à un flux QUIC rejeté par un ICMP Port Unreachable, afin que le client bascule immédiatement au lieu d'attendre l'expiration d'un délai.

Envoyer la requête ailleurs

Deux moyens, sur deux couches :

[Host]
api.example.com = 10.0.0.5

Un mappage [Host] répond à la résolution DNS par l'adresse de votre choix — une machine de préproduction, ou une adresse qui ne mène nulle part si ce que vous voulez est une connexion qui expire plutôt qu'une connexion refusée. Il vaut pour tous les protocoles et ne demande aucun déchiffrement. Purgez le cache DNS après l'avoir changé.

[URL Rewrite]
^https://api\.example\.com/v1/(.*) https://staging.example.com/v1/$1 header

Le mode header réécrit la requête sur place et corrige l'en-tête Host. Celui-ci est au niveau HTTP et demande donc le déchiffrement pour le HTTPS. Le client ne s'aperçoit jamais de la réécriture tant que la requête conserve son hôte. Une requête HTTPS déchiffrée est liée à l'hôte auquel elle a été envoyée : lorsqu'une règle désigne un autre hôte — comme celle ci-dessus —, la requête reçoit donc une réponse 307 vers la nouvelle URL, que le client suit lui-même ; Chute répond de la même façon partout ailleurs où la destination ne peut pas être réécrite sur place.

Confirmer que cela a bien agi

Une règle qui ne correspond jamais ressemble exactement à une règle qui a correspondu sans rien faire — c'est le mode de défaillance propre à tout ce que décrit cette page.

  • Réécritures et simulations : la page Règles de la Console web liste chaque règle de réécriture d'URL, d'en-tête et de corps, et de réponse simulée qui a agi pendant cette exécution, avec un compteur. Absente de la liste signifie jamais correspondu. Les mêmes données se trouvent dans rewrite_hits de GET /api/rules.
  • Par connexion : ouvrez la connexion dans la console et lisez ses lignes Réécritures appliquées, qui nomment la règle dans ses propres termes. Dans Dashboard, l'onglet Général de la connexion les compte comme Déclenchées sous Règles et les nomme dans la liste Règles déclenchées.
  • Les scripts n'apparaissent pas dans ce tableau. La preuve d'un script, c'est sa propre sortie : les lignes console.log arrivent dans le journal, lisibles sur la page Journal de la console ou via GET /api/logs. Elles sont écrites au niveau verbose : passez donc d'abord loglevel à verbose — PUT /api/loglevel le fait sans rechargement ; console.warn et console.error apparaissent au niveau par défaut. Chaque ligne est tronquée à 256 caractères, et les identifiants y sont masqués.

Faire le ménage

Les règles ajoutées depuis la console, depuis Dashboard ou via POST /api/rewrites/:family vivent dans le moteur en cours d'exécution et disparaissent au redémarrage suivant : idéal pour une expérience, mauvais endroit pour quelque chose dont vous dépendez. Les règles du fichier de configuration survivent aux redémarrages, ce qui en fait un bon endroit pour une simulation et une très mauvaise chose à oublier : une ligne [Map Local] laissée dans une configuration répondra encore aux requêtes des semaines plus tard, et cela ressemble en tout point à un serveur cassé.

S. Smart Rabbit LLC © All Rights Reserved            updated 2026-09-30 15:11:19

results matching ""

    No results matching ""