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 d'accès 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 une connexion net, 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 un lien lent 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 JSON vide | reject-200, reject-img, reject-dict |
Réécriture d'URL |
| Tout autre statut — 401, 429, 500 | Un script http-request |
Scripts JS |
| De la latence | Un script http-request ou http-response |
Scripts JS |
| Un point d'accès simplement injoignable | Une règle REJECT |
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 du base64 en ligne, sans interroger le vrai serveur :
[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 doit correspondre à toute l'URL, pas à une partie. Terminez le motif par
.*, sauf si vous visez précisément une URL sans aucune chaîne de requête. 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-ybase64=, ou servez le fichier en HTTP et passez par la réécriture d'URL.- Le statut est toujours
200 OK.[Map Local]n'a aucun moyen d'en fixer un, et la connexion est fermée après la réponse. Pour tout autre statut, prenez un script — voir ci-dessous.
Le corps prend en charge les variables de gabarit {{ "{{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 (un GIF 1×1), reject-dict ({} 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, un script http-request court-circuite 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, contrairement aux familles de réécriture : un préfixe comme ^https://api\.example\.com/v1/orders suffit, et un .* final est inutile.
Sur le chemin HTTP/1.1, la ligne de statut est écrite avec la raison
OKquel que soit le code (HTTP/1.1 429 OK). Les clients lisent le nombre, pas le texte : c'est donc cosmétique — mais c'est ce que vous verrez dans une capture brute.
La latence
Un script retient le message jusqu'à ce qu'il appelle $done() : une minuterie est donc un délai.
[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 un laissez-passer — le message poursuit sa route sans modification —, 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 d'accès 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
REJECT-DROP, REJECT-TINYGIF et REJECT-NO-DROP sont acceptés par compatibilité et se comportent tous comme un REJECT ordinaire. 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, si bien que le client n'apprend jamais qu'il a été redirigé. Celui-ci est au niveau HTTP et demande donc le déchiffrement pour le HTTPS. Lorsque la destination ne peut pas être réécrite sur place, Chute se rabat sur une réponse 307 vers la nouvelle URL.
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 la manière dont toute cette page échoue.
- Réécritures et simulations : la page Règles de la Console web liste chaque règle URL Rewrite, Header Rewrite, Body Rewrite et Map Local 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_hitsdeGET /api/rules. - Par connexion : ouvrez la connexion dans la console ou dans Dashboard et lisez ses lignes Réécritures appliquées, qui nomment la règle dans ses propres termes.
- Les scripts n'apparaissent pas dans ce tableau. La preuve d'un script, c'est sa propre sortie : les lignes
console.logarrivent dans le journal, lisibles sur la page Journaux de la console ou viaGET /api/logs.
Faire le ménage
Les règles ajoutées depuis la console, depuis Dashboard ou via POST /api/rewrites/:family vivent dans le noyau 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é.