المعترضات
جزء من قسم TypeScript وNestJS في رحلة Node.js على Coddy. الدرس 23 من 24.
تريد أن توضّح كل إجابة المدة التي استغرقتها، من دون تعديل كل معالج. يغلّف المعترض المعالج: يُنفَّذ الرمز البرمجي قبل next.handle() قبله، ويُنفَّذ الرمز البرمجي على ما تُعيده next.handle() بعده.
intercept(context: ExecutionContext, next: CallHandler) {
console.log('before');
return next.handle().pipe(
tap(() => console.log('after')),
);
}تعيد next.handle() نتيجة المعالج على هيئة قيمة قابلة للملاحظة من RxJS، وهي قيمة تصل لاحقًا. تنفّذ .pipe(tap(...)) دالةً عند وصول القيمة، قبل أن يرسلها Nest؛ ولا علاقة لهذه pipe بأنابيب التحقق في Nest. تعمل الحراس قبل أي معترض، لذا فإن الطلب الذي يرفضه أحد الحراس لا يصل إليه أبدًا. أَعِد القيمة القابلة للملاحظة التي مرّت عبر الأنبوب: يرسل Nest ما تحمله. تعيد context.switchToHttp().getResponse() القيمة res، التي تضيف ترويسةً باستخدام setHeader.
يطبّق app.useGlobalInterceptors(...) معترضًا على كل مسار؛ أما @UseInterceptors(...) فيطبّقه على متحكّم واحد.
التحدي
سهليبدأ Run متتبّعك من جديد باستخدام src/main.ts. يمكن أن تبقى ملفات التدريب المنفصلة في شجرة الملفات؛ فهذا الخادم لا يبدأ تشغيلها.
يريد العملاء وأدوات المراقبة الخاصة بك معرفة المدة التي استغرقها كل طلب. أضف ترويسة استجابة، X-Response-Time، إلى كل استجابة ناجحة، من دون تغيير أي مسار.
- افتح
src/common/response-time.interceptor.ts. في الطريقةinterceptضمنResponseTimeInterceptor، سجّل الوقت الحالي أولًا باستخدامDate.now(). يُنفَّذ الرمز في أعلىinterceptقبل طريقة المسار. - احصل على
resالخاص بـ Express باستخدامcontext.switchToHttp().getResponse(). - تنفّذ
next.handle()طريقة المسار، وتعيد نتيجتها على هيئة Observable من RxJS: قيمة تصل لاحقًا. واصل إعادتها، لكن مرّرها عبرtap، المستوردة بالفعل منrxjs، باستخدام طريقة Observable المسماة.pipe(). تُنفّذ الدالة التي تمرّرها إلىtapعند وصول النتيجة، قبل أن يرسلها Nest. استدعِ في تلك الدالةres.setHeaderلضبطX-Response-Timeعلى عدد المللي ثانية المنقضية منذ تسجيلك للوقت، متبوعًا بـms، مثل3ms. (لا علاقة لـ.pipe()في RxJS بأنابيب التحقق في الفصل 3؛ فهي تربط خطوةً بـ Observable.) - في
src/main.ts، سجّل المعترض للتطبيق بأكمله باستخدامapp.useGlobalInterceptors(...)، ومرّر إليه كائنًا جديدًا منResponseTimeInterceptor، بجوار الأنبوب والمرشّح. - يجب ألا تتغير أجسام الاستجابات. يظل
GET /tasksيعيد{ "items": [...], "page": 1, "total": 3, "pages": 2 }.
جرّب ذلك: أرسل GET /tasks وافتح علامة تبويب Headers في الاستجابة. سترى X-Response-Time مع قيمة مثل 0ms أو 2ms. يظهر أيضًا مع GET /stats، وكذلك مع طلب POST يتضمن { "title": "Buy milk" } ويُرسل مع الترويسة x-api-key: tasks-key-123. ثم أرسل طلب POST هذا من دون المفتاح: ستحصل على 403، ولن تظهر X-Response-Time. يشغّل Nest الحراس قبل المعترضات، لذا رفض الحارس الطلب قبل أن يبدأ المعترض عمله. كما لن تظهر الترويسة إذا حدث خطأ داخل أحد المسارات، لأن tap لا يُنفّذ إلا عند وصول نتيجة.
جرّب بنفسك
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable, tap } from 'rxjs';
@Injectable()
export class ResponseTimeInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
// سجّل الوقت، ودع المعالج يعمل، ثم اضبط X-Response-Time.
return next.handle();
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس TypeScript وNestJS
تدرّب بنفسك: Playground لـ Express