การจำลองและการแทรกความผิดพลาด
การเฝ้าดูทราฟฟิกตอบว่าแอปทำอะไร หน้านี้ว่าด้วยอีกครึ่งหนึ่ง: ทำให้เครือข่ายตอบสิ่งที่ปกติมันจะไม่ตอบ เพื่อดูว่าแอปจะจัดการอย่างไร แบ็กเอนด์ที่ยังไม่ได้สร้าง จุดปลายทางที่คืน 500 การตอบกลับที่ใช้เวลาแปดวินาที API ที่หายไปเฉย ๆ
ทุกอย่างในหน้านี้ทำงานบนทุกแพลตฟอร์มที่ Chute รันได้ และทุกอย่างที่แตะ HTTPS ต้องเปิดการถอดรหัสสำหรับโฮสต์นั้นก่อน — คำขอที่เข้ารหัสซึ่ง Chute อ่านไม่ได้ ก็เป็นคำขอที่ Chute ตอบแทนไม่ได้
Chute จำลองอะไรได้และไม่ได้
Chute แทรกแซงที่การเชื่อมต่อและที่ข้อความ HTTP มันไม่มีตัวจัดรูปทราฟฟิก ดังนั้น:
| ทำได้ | คืนเนื้อหาสำเร็จรูป คืนรหัสสถานะที่เลือก ใส่ความหน่วงคงที่ก่อนคำขอหรือการตอบกลับ ปฏิเสธการเชื่อมต่อไปเลย บังคับไคลเอนต์ HTTP/3 ให้กลับไปใช้ TCP ส่งคำขอไปยังโฮสต์อื่นที่ไม่ใช่โฮสต์ที่ขอ |
| ทำไม่ได้ | จำกัดแบนด์วิดท์ ทิ้งหรือสลับลำดับแพ็กเก็ต เพิ่มจิตเตอร์ ทำให้การเชื่อมต่อแย่ลงกลางคัน หรือจำลอง RTT ที่เจาะจงในระดับชั้นขนส่ง |
ไม่มีส่วน [Throttle] ในการกำหนดค่า และไม่มีการจำกัดความเร็วที่ไหนเลย ถ้าคุณต้องการลิงก์ที่ช้าแทนที่จะเป็นการตอบกลับที่ช้า นั่นเป็นเรื่องของตัวจำลองสภาพเครือข่าย (Network Link Conditioner ของ Apple หรือเราเตอร์) ไม่ใช่ของ Chute
เลือกกลไก
| สิ่งที่จะจำลอง | ใช้ | ที่ไหน |
|---|---|---|
| เนื้อหาการตอบกลับที่ยังไม่มีอยู่จริง | [Map Local] |
การตอบกลับจำลอง |
| 503 พอดี | [URL Rewrite] … reject |
URL Rewrite |
| 200 ที่ว่างเปล่า รูปเปล่า อ็อบเจกต์หรืออาร์เรย์ JSON ว่าง | reject-200, reject-img / reject-tinygif, reject-dict, reject-array |
URL Rewrite |
| รหัสสถานะอื่น ๆ — 401, 429, 500 | [Map Local] ที่มี status-code= หรือสคริปต์ http-request |
การตอบกลับจำลอง, การเขียนสคริปต์ JS |
| ความหน่วง | สคริปต์ http-request หรือ http-response |
การเขียนสคริปต์ JS |
| จุดปลายทางที่เข้าไม่ถึงเลย | กฎ REJECT |
นโยบายในตัว |
| คำขอที่ค้างอยู่จนไคลเอนต์ยอมแพ้ | กฎ REJECT-DROP |
นโยบายในตัว |
| ไคลเอนต์ที่ไม่ยอมถอยไปใช้ TCP | block-quic |
ตัวเลือกเบ็ดเตล็ด |
| แบ็กเอนด์อื่นหลัง URL เดิม | [Host] หรือโหมด header ของ [URL Rewrite] |
การกำหนด 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="
สามสิ่งที่ตัดสินว่ามันจะได้ผลหรือไม่:
- นิพจน์ปกติจับคู่ที่ตำแหน่งใดก็ได้ใน URL เช่นเดียวกับ URL Rewrite:
^https://api\.example\.com/v1/profileครอบคลุมพาธนั้นและสตริงคิวรีใด ๆ ที่ตามมาอยู่แล้ว จึงไม่ต้องเติม.*ท้าย data=ถูกอ่านโดยอุปกรณ์ที่รัน Chute บน Mac นั่นสะดวก — แก้ไฟล์แล้วคำขอถัดไปก็เห็นการเปลี่ยนแปลง บนโทรศัพท์หรือ Apple TV พาธจาก Mac ของคุณไม่มีความหมาย ให้ใช้base64=แทน หรือเสิร์ฟไฟล์ผ่าน HTTP แล้วใช้ URL Rewrite- สถานะเป็น
200 OKเว้นแต่คุณจะกำหนดstatus-code=และการเชื่อมต่อจะถูกปิดหลังตอบ ส่วนหัวการตอบกลับใส่ไว้ในheader="Key: value|…"
เนื้อหารองรับตัวแปรเทมเพลต {{ "{{url}}" }}, {{ "{{host}}" }}, {{ "{{path}}" }}, {{ "{{method}}" }} และ {{ "{{ua}}" }} ซึ่งเพียงพอสำหรับทำการจำลองที่สะท้อนกลับว่าถูกถามอะไรมา
รหัสสถานะข้อผิดพลาด
สำหรับ 503 ไม่ต้องใช้สคริปต์ — URL rewrite โหมด reject คืน HTTP/1.1 503:
[URL Rewrite]
^https://api\.example\.com/v1/orders.* _ reject
โหมดพี่น้องครอบคลุมรูปแบบอื่นของ "ไม่มีอะไรใช้ได้": reject-200 (200 พร้อมเนื้อหาว่าง), reject-img และ reject-tinygif (GIF ขนาด 1×1), 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 จึงไม่ได้ล้มเหลวอย่างเอิกเกริก มันแค่เลิกหน่วงเท่านั้น ตั้ง 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 ที่สร้างขึ้น (ว่าง, {}, [], GIF ขนาด 1×1, MP4 เปล่า) REJECT, REJECT-TINYGIF และ REJECT-IMG จะกลายเป็น REJECT-DROP เป็นเวลา 30 วินาทีเมื่อโฮสต์เดียวถูกปฏิเสธเกิน 50 ครั้งภายใน 30 วินาที ส่วน 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 Port Unreachable ไคลเอนต์จึงถอยกลับทันทีแทนที่จะรอจนหมดเวลา
ส่งคำขอไปที่อื่น
สองวิธีในสองชั้น:
[Host]
api.example.com = 10.0.0.5
การแมป [Host] ตอบการค้นหา DNS ด้วยที่อยู่ที่คุณเลือก — เครื่อง staging หรือที่อยู่ที่ไม่ไปไหนเลย ถ้าสิ่งที่คุณต้องการคือการเชื่อมต่อที่หมดเวลาแทนที่จะถูกปฏิเสธ มันมีผลกับทุกโปรโตคอลและไม่ต้องถอดรหัส ล้างแคช DNS หลังเปลี่ยน
[URL Rewrite]
^https://api\.example\.com/v1/(.*) https://staging.example.com/v1/$1 header
โหมด header เขียนคำขอใหม่ในที่และแก้ส่วนหัว Host ให้ด้วย วิธีนี้อยู่ระดับ HTTP จึงต้องถอดรหัสสำหรับ HTTPS ไคลเอนต์จะไม่รู้เลยว่ามีการเขียนใหม่ ตราบใดที่คำขอยังคงใช้โฮสต์เดิม คำขอ HTTPS ที่ถอดรหัสแล้วผูกอยู่กับโฮสต์ที่มันถูกส่งไปหา ดังนั้นกฎที่ระบุโฮสต์อื่น — อย่างกฎข้างบน — จะได้รับการตอบด้วย 307 ชี้ไป URL ใหม่ ซึ่งไคลเอนต์จะตามไปเอง และในกรณีอื่นใดที่เขียนปลายทางใหม่ในที่ไม่ได้ Chute ก็ตอบด้วยวิธีเดียวกัน
ยืนยันว่ามันทำงานจริง
กฎที่ไม่เคยตรงเลยหน้าตาเหมือนกฎที่ตรงแล้วไม่ทำอะไรทุกประการ — และนี่คือรูปแบบความล้มเหลวของทุกอย่างในหน้านี้
- การเขียนใหม่และการจำลอง: หน้า กฎ ของคอนโซลเว็บแสดงรายการกฎ URL Rewrite, Header Rewrite, Body Rewrite และการตอบกลับจำลองทุกข้อที่ทำงานในรอบนี้พร้อมจำนวนครั้ง ถ้าไม่อยู่ในรายการแปลว่ามันไม่เคยตรงเลย ข้อมูลชุดเดียวกันอยู่ใน
rewrite_hitsของGET /api/rules - รายการเชื่อมต่อ: เปิดการเชื่อมต่อนั้นในคอนโซลแล้วอ่านแถว การเขียนใหม่ที่ใช้ ซึ่งเรียกชื่อกฎด้วยถ้อยคำของกฎเอง ใน Dashboard แท็บ ทั่วไป ของการเชื่อมต่อจะนับกฎเหล่านี้เป็น ทำงาน ใต้หัวข้อ กฎ และระบุชื่อไว้ในรายการ กฎที่ทำงาน
- สคริปต์ไม่ปรากฏในตารางนั้น หลักฐานของสคริปต์คือผลลัพธ์ของตัวมันเอง บรรทัด
console.logจะลงในบันทึก อ่านได้ที่หน้า บันทึก ของคอนโซลหรือด้วยGET /api/logsบรรทัดเหล่านี้ถูกเขียนที่ระดับ verbose ดังนั้นให้เพิ่มloglevelเป็นverboseก่อน —PUT /api/loglevelทำได้โดยไม่ต้องโหลดใหม่ ส่วนconsole.warnและconsole.errorจะแสดงที่ระดับค่าเริ่มต้น แต่ละบรรทัดจะถูกตัดให้เหลือ 256 อักขระ โดยข้อมูลรับรองจะถูกปิดบังไว้
เก็บกวาด
กฎที่เพิ่มจากคอนโซล จาก Dashboard หรือผ่าน POST /api/rewrites/:family อยู่ในเอนจินที่กำลังทำงาน และหายไปเมื่อรีสตาร์ตครั้งถัดไป ซึ่งเหมาะกับการทดลอง แต่เป็นที่แย่มากสำหรับเก็บสิ่งที่คุณต้องพึ่งพา กฎในไฟล์การกำหนดค่าอยู่รอดข้ามการรีสตาร์ต จึงเป็นที่ดีสำหรับเก็บการจำลอง และเป็นสิ่งที่ไม่ควรลืมอย่างยิ่ง: บรรทัด [Map Local] ที่ทิ้งไว้ในการกำหนดค่ายังคงตอบคำขออยู่อีกหลายสัปดาห์ถัดมา และหน้าตาเหมือนเซิร์ฟเวอร์ที่พังทุกประการ
หน้านี้เป็นฉบับแปลจากเวอร์ชันภาษาอังกฤษ หากเนื้อหาไม่ตรงกัน ให้ยึดเวอร์ชันภาษาอังกฤษเป็นหลัก