المحاكاة وحقن الأعطال
مراقبة الحركة تجيب عمّا يفعله التطبيق. وهذه الصفحة عن النصف الآخر: أن تجعل الشبكة تجيب بما لم تكن لتجيب به، لترى ماذا يفعل التطبيق حياله. خادم خلفي لم يُبنَ بعد، ونقطة نهاية تُرجع 500، واستجابة تستغرق ثماني ثوانٍ، وواجهة برمجية اختفت ببساطة.
كل ما هنا يعمل على كل منصّة يعمل عليها Chute، وكل ما يمسّ HTTPS يحتاج أولاً إلى تفعيل فك التشفير لذلك المضيف — فالطلب المشفَّر الذي لا يستطيع Chute قراءته لا يستطيع الإجابة عنه.
ما يستطيع Chute محاكاته وما لا يستطيع
يتدخّل Chute عند الاتصال وعند رسالة HTTP. وليس لديه مُشكّل حركة، ولذلك:
| يستطيع | إرجاع محتوى جاهز، وإرجاع رمز حالة تختاره، وإضافة تأخير ثابت قبل طلب أو استجابة، ورفض الاتصال رفضاً قاطعاً، وإعادة عملاء HTTP/3 إلى TCP، وإرسال طلب إلى مضيف غير الذي طُلب |
| لا يستطيع | تحديد عرض النطاق، أو إسقاط الحزم أو إعادة ترتيبها، أو إضافة تذبذب (jitter)، أو تدهور اتصال في منتصفه، أو محاكاة زمن ذهاب وإياب معيّن على مستوى النقل |
لا يوجد قسم [Throttle] في الإعدادات ولا حدّ سرعة في أي موضع. وإن كنت تحتاج وصلة بطيئة لا استجابة بطيئة، فذلك من شأن مُكيّف شبكة (Network Link Conditioner من Apple، أو موجّه)، لا من شأن Chute.
اختر آلية
| لمحاكاة | استخدم | أين |
|---|---|---|
| محتوى استجابة غير موجود بعد | [Map Local] |
الاستجابة الوهمية |
| 503 بالضبط | [URL Rewrite] … reject |
إعادة كتابة الرابط |
| 200 فارغ، أو صورة فارغة، أو كائن أو مصفوفة JSON فارغة | reject-200 وreject-img / reject-tinygif وreject-dict وreject-array |
إعادة كتابة الرابط |
| أي حالة أخرى — 401 و429 و500 | [Map Local] مع status-code=، أو سكريبت http-request |
الاستجابة الوهمية، برمجة JavaScript |
| زمن استجابة | سكريبت http-request أو http-response |
برمجة JavaScript |
| نقطة نهاية غير قابلة للوصول ببساطة | قاعدة REJECT |
السياسة المدمجة |
| طلب يبقى معلّقاً حتى يستسلم العميل | قاعدة REJECT-DROP |
السياسة المدمجة |
| عميل لا يتراجع إلى TCP | block-quic |
خيارات متنوعة |
| خادم خلفي مختلف خلف الرابط نفسه | [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="
ثلاثة أمور تحدّد نجاح ذلك:
- يُطابَق التعبير النمطي في أي موضع من الرابط، كما في إعادة كتابة الرابط:
^https://api\.example\.com/v1/profileيغطي بالفعل المسار وأي سلسلة استعلام بعده، فلا حاجة إلى.*في آخره. - يقرأ
data=الجهازُ الذي يشغّل Chute. وعلى Mac هذا مريح — عدّل الملف فيرى الطلب التالي التغيير. أما على الهاتف أو Apple TV فمسار من جهاز Mac لديك لا يعني شيئاً؛ استخدم هناكbase64=، أو قدّم الملف عبر HTTP واستعمل إعادة كتابة الرابط. - الحالة
200 OKما لم تحددstatus-code=، والاتصال يُغلق بعد الإجابة. وتُكتب ترويسات الاستجابة فيheader="Key: value|…".
ويدعم المحتوى متغيّرات القوالب {{ "{{url}}" }} و{{ "{{host}}" }} و{{ "{{path}}" }} و{{ "{{method}}" }} و{{ "{{ua}}" }}، وهذا يكفي لصنع محاكاة تردّ ما سُئلت عنه.
رمز حالة خطأ
لأجل 503 لا حاجة إلى سكريبت — فإعادة كتابة رابط بنمط الرفض (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 السكريبت في أي موضع من الرابط، كما في عائلات إعادة الكتابة — فبادئة مثل ^https://api\.example\.com/v1/orders تكفي، ولا حاجة إلى .* في آخرها.
في HTTP/1.1 يحمل سطر الحالة عبارة السبب القياسية للرمز (
HTTP/1.1 429 Too Many Requests)؛ أما الرمز الذي لا يسمّيه سجل IANA للرموز، مثل 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 — إعادة الاستجابة الحقيقية متأخرة ثماني ثوانٍ
setTimeout(function () {
$done({})
}, 8)
والميزانية هي timeout السكريبت نفسه: خمس ثوانٍ افتراضياً، وأي قيمة فوق 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 مُصطنعة (فارغة، أو {}، أو []، أو 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 بعنوان تختاره أنت — جهاز تجريبي، أو عنوان لا يؤدي إلى شيء إن كنت تريد اتصالاً تنتهي مهلته بدل اتصال مرفوض. وهو يسري على كل البروتوكولات ولا يحتاج فك تشفير. وامسح ذاكرة DNS بعد تغييره.
[URL Rewrite]
^https://api\.example\.com/v1/(.*) https://staging.example.com/v1/$1 header
يعيد نمط الترويسة كتابة الطلب في مكانه ويصلح ترويسة Host معه. وهذه في طبقة HTTP فتحتاج فك التشفير مع HTTPS. ولا يعلم العميل بإعادة الكتابة ما دام الطلب يحتفظ بمضيفه. فطلب HTTPS المفكوك تشفيره مرتبط بالمضيف الذي أُرسل إليه، لذا فالقاعدة التي تسمّي مضيفاً آخر — مثل القاعدة أعلاه — يُجاب عنها برمز 307 نحو الرابط الجديد، ويتبعه العميل بنفسه؛ ويجيب Chute بالطريقة نفسها في كل موضع آخر يتعذّر فيه إعادة كتابة الوجهة في مكانها.
التأكد من أنها عملت
القاعدة التي لا تطابق قط تبدو تماماً كقاعدة طابقت ولم تفعل شيئاً — وهذا هو نمط الفشل المشترك لكل ما في هذه الصفحة.
- إعادات الكتابة والاستجابة الوهمية: تسرد صفحة القواعد في وحدة تحكم الويب كل قاعدة من قواعد إعادة كتابة الرابط وإعادة كتابة الترويسات وإعادة كتابة المحتوى والاستجابة الوهمية عملت في هذا التشغيل، مع عدّادها. وغيابها عن القائمة يعني أنها لم تطابق قط. والبيانات نفسها في
rewrite_hitsضمنGET /api/rules. - لكل اتصال: افتح الاتصال في وحدة التحكم واقرأ صفوف إعادات الكتابة المطبّقة، فهي تسمّي القاعدة بكلماتها هي. وفي Dashboard يعدّها تبويب عام (General) للاتصال في تم التنفيذ (Fired) تحت القواعد (Rules)، ويسمّيها في قائمة القواعد المنفَّذة (Rules Fired).
- السكريبتات لا تظهر في ذلك الجدول. دليل السكريبت هو مخرجاته: تصل أسطر
console.logإلى السجل، وتُقرأ في صفحة السجلات بوحدة التحكم أو عبرGET /api/logs. وهي تُكتب على المستوى التفصيلي، لذا ارفعloglevelإلىverboseأولاً — ويفعلPUT /api/loglevelذلك دون إعادة تحميل؛ أماconsole.warnوconsole.errorفيظهران على المستوى الافتراضي. ويُقتطع كل سطر عند 256 حرفاً، مع حجب بيانات الاعتماد.
التنظيف بعد الانتهاء
القواعد المضافة من وحدة التحكم أو من Dashboard أو عبر POST /api/rewrites/:family تعيش في المحرك العامل وتزول عند إعادة التشغيل التالية — وهذا مثالي لتجربة وسيّئ جداً لشيء تعتمد عليه. أما قواعد ملف الإعدادات فتنجو من إعادة التشغيل، وهذا يجعله مكاناً جيداً لمحاكاة، وشيئاً لا ينبغي نسيانه أبداً: سطر [Map Local] متروك في الإعدادات سيظل يجيب عن الطلبات بعد أسابيع، وهو يبدو تماماً كخادم معطّل.
هذه الصفحة ترجمة للنسخة الإنجليزية. في حال وجود اختلاف، يُعتمد على النسخة الإنجليزية.
ملاحظة: التطبيق لا يدعم اللغة العربية حاليًا.