البرمجة متوسط 8 دقيقة

كيفية تحديد معدل الطلبات في واجهة Node.js

بدون تحديد معدل الطلبات، تكون واجهة API الخاصة بك عرضة لهجمات القوة الغاشمة على نقاط تسجيل الدخول، والمتتبعات العدوانية التي تُثقل قاعدة البيانات، والحلقات اللانهائية غير المقصودة في كود العميل التي قد تُعطّل الخادم. تحديد معدل الطلبات هو خط الدفاع الأول — سهل التطبيق وفعّال فوراً.

الخطوات

  1. 1

    تثبيت express-rate-limit

    express-rate-limit هي الحزمة القياسية لهذا الغرض. يحظى بدعم من منظومة Express ويغطي الحالات الشائعة بأدنى قدر من الإعداد.

    bash
    npm install express-rate-limit
  2. 2

    تطبيق حد عام على جميع المسارات

    يمنع الحد العام الفضفاض العملاء المتهورين من إغراق الخادم قبل وصولهم إلى نقاط حساسة. أضفه قبل تعريفات المسارات.

    javascript
    const express = require('express');
    const rateLimit = require('express-rate-limit');
    
    const app = express();
    
    const globalLimiter = rateLimit({
      windowMs: 15 * 60 * 1000, // 15 دقيقة
      max: 200,                  // عدد الطلبات لكل نافذة زمنية لكل IP
      standardHeaders: true,     // إرسال ترويسات RateLimit-*
      legacyHeaders: false,
      message: { error: { code: 'TOO_MANY_REQUESTS', message: 'تمهّل.' } },
    });
    
    app.use(globalLimiter);
  3. 3

    تشديد الحد على نقاط المصادقة

    نقاط تسجيل الدخول وإعادة تعيين كلمة المرور هي أهداف للقوة الغاشمة. طبّق معدلاً أكثر صرامة مباشرةً على هذه المسارات. خمس محاولات في 15 دقيقة حد معقول لنقطة تسجيل الدخول.

    javascript
    const authLimiter = rateLimit({
      windowMs: 15 * 60 * 1000,
      max: 5,
      skipSuccessfulRequests: true,
      message: {
        error: {
          code: 'AUTH_RATE_LIMITED',
          message: 'محاولات تسجيل دخول كثيرة جداً. أعد المحاولة بعد 15 دقيقة.',
        },
      },
    });
    
    app.post('/auth/login', authLimiter, loginHandler);
    app.post('/auth/forgot-password', authLimiter, forgotPasswordHandler);
  4. 4

    التمييز بين IP ومعرّف المستخدم

    المفتاح الافتراضي هو عنوان IP. في المسارات المصادَق عليها، يمنع استخدام معرّف المستخدم كمفتاح تجاوزَ الحد عبر تغيير عناوين IP. أعد معرّف المستخدم إن وُجد، وإلا فارجع إلى IP.

    javascript
    const userAwareLimiter = rateLimit({
      windowMs: 60 * 1000,
      max: 60,
      keyGenerator: (req) => {
        return req.user ? `user:${req.user.id}` : req.ip;
      },
    });
    
    app.use('/api', authenticateOptional, userAwareLimiter);
  5. 5

    فهم ترويسات استجابة RateLimit

    عند تفعيل standardHeaders: true، تحمل كل استجابة:

    • RateLimit-Limit — الحد الأقصى للطلبات المسموح بها
    • RateLimit-Remaining — الطلبات المتبقية في النافذة الحالية
    • RateLimit-Reset — توقيت Unix لإعادة تعيين النافذة

    عند تجاوز الحد، ترسل المكتبة HTTP 429 وترويسة Retry-After. العملاء المحترمون يحترمونها؛ سجّل من لا يلتزم وافصله.

  6. 6

    استخدام Redis لبيئات متعددة الحالات

    التخزين الافتراضي في الذاكرة لا يشارك الحالة بين العمليات أو الأجهزة. إذا شغّلت أكثر من عملية Node واحدة (cluster أو PM2 أو Kubernetes)، يحسب كل مثيل بشكل مستقل، مما يُضاعف الحد فعلياً. انتقل إلى مخزن مدعوم بـ Redis.

    bash
    npm install rate-limit-redis ioredis
  7. 7

    إعداد مخزن Redis

    مرّر خيار المخزن إلى المُقيِّد. سيعيش العداد الآن في Redis ويُشارَك بين جميع الحالات.

    javascript
    const { RedisStore } = require('rate-limit-redis');
    const Redis = require('ioredis');
    
    const redisClient = new Redis(process.env.REDIS_URL);
    
    const globalLimiter = rateLimit({
      windowMs: 15 * 60 * 1000,
      max: 200,
      standardHeaders: true,
      legacyHeaders: false,
      store: new RedisStore({
        sendCommand: (...args) => redisClient.call(...args),
      }),
    });
  8. 8

    تسجيل أحداث تجاوز الحد ومراقبتها

    معرفة من يتجاوز الحود لا تقل أهمية عن تطبيقها. تجاوز handler بدالة تُسجّل الحدث قبل إرسال استجابة 429. أدخل هذه السجلات إلى نظام المراقبة (Datadog أو Grafana أو Logtail) وأنشئ تنبيهات على الارتفاعات المفاجئة.

    javascript
    const logger = require('./logger');
    
    const globalLimiter = rateLimit({
      windowMs: 15 * 60 * 1000,
      max: 200,
      standardHeaders: true,
      legacyHeaders: false,
      handler: (req, res, next, options) => {
        logger.warn({
          event: 'rate_limit_exceeded',
          ip: req.ip,
          userId: req.user?.id ?? null,
          path: req.path,
          method: req.method,
        });
        res.status(options.statusCode).json(options.message);
      },
    });

نصائح ومحاذير

  • اضبط <code>trustProxy: true</code> أو <code>app.set('trust proxy', 1)</code> عندما يعمل تطبيقك خلف Nginx أو موازن تحميل، وإلا سيكون <code>req.ip</code> دائماً عنوان الوكيل.
  • استخدم <code>skip</code> لاستثناء مسارات فحص الصحة الداخلية أو عناوين IP لمنظومة المراقبة كي لا تُفعّل الحد أبداً.
  • التخزين في الذاكرة مناسب لبيئة التطوير أحادية العملية، لكن استخدم Redis دائماً في بيئة الاختبار والإنتاج.
  • لا تضع حدوداً مشددة إلى درجة تُفعَّل في الاستخدام العادي — حلّل أنماط حركة المرور الفعلية أولاً.

خاتمة

تحديد معدل الطلبات ضرورة أساسية لأي واجهة API عامة. طبّق حداً عاماً متساهلاً، وحداً صارماً على مسارات المصادقة، وميّز بين المستخدمين باستخدام معرّفاتهم عند المصادقة، ثم ادعمه بـ Redis فور التوسع إلى أكثر من عملية واحدة. يستغرق الإعداد بالكامل أقل من ساعة ويحميك من طيف واسع من الإساءات والأحمال غير المتوقعة.

#Node.js #API #Security
العودة إلى جميع الأدلة

هل تحتاج مساعدة في مشروعك؟

احجز استشارة مجانية لمدة 30 دقيقة لمناقشة تحدياتك التقنية واستكشاف الحلول معًا.