모의 응답과 장애 주입
트래픽을 지켜보면 앱이 무엇을 하는지 알 수 있습니다. 이 페이지는 나머지 절반입니다: 네트워크가 원래라면 하지 않았을 답을 하게 만들고, 앱이 그것에 어떻게 대응하는지 보는 것. 아직 만들어지지 않은 백엔드, 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="
이것이 통하는지는 세 가지가 결정합니다:
- 정규식은 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 포트 도달 불가로 답하므로, 클라이언트가 타임아웃을 기다리지 않고 곧바로 물러섭니다.
요청을 다른 곳으로 보내기
두 계층에 두 가지 방법이 있습니다:
[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로 답하도록 물러섭니다.
정말 작동했는지 확인하기
한 번도 일치하지 않은 규칙은 일치했지만 아무것도 하지 않은 규칙과 똑같이 보입니다 — 이 페이지 전체가 실패하는 방식이 그것입니다.
- 재작성과 모의 응답: 웹 콘솔의 규칙 페이지가 이번 실행에서 작동한 모든 URL 재작성·헤더 재작성·본문 재작성·Map Local 규칙을 횟수와 함께 나열합니다. 거기 없는 규칙은 한 번도 일치하지 않았습니다. 같은 데이터가
GET /api/rules의rewrite_hits에 있습니다. - 연결별로: 콘솔이나 Dashboard에서 그 연결을 열어 적용된 규칙 행을 읽으십시오. 규칙 자신의 문장으로 규칙을 알려 줍니다.
- 스크립트는 그 표에 나오지 않습니다. 스크립트의 증거는 스크립트 자신의 출력입니다.
console.log줄은 로그에 남고, 콘솔의 로그 페이지나GET /api/logs에서 읽을 수 있습니다.
뒷정리
콘솔이나 Dashboard에서, 또는 POST /api/rewrites/:family로 추가한 규칙은 실행 중인 커널 안에 살고 다음 재시작에 사라집니다 — 실험에는 이상적이지만 믿고 쓸 것을 두기에는 나쁜 곳입니다. 설정 파일 안의 규칙은 재시작을 견디므로 모의 응답을 두기에 좋은 곳이고, 동시에 잊어서는 안 될 것이기도 합니다: 설정에 남겨진 [Map Local] 한 줄은 몇 주 뒤에도 여전히 요청에 답하고 있으며, 그것은 고장 난 서버와 똑같이 보입니다.
이 페이지는 영어판의 번역본입니다. 내용이 다를 경우 영어판이 우선합니다.