برنامه نویسی

API Throttling در Node.js

API Throttling در Node.js

فهرست مطالب


API Throttling در Node.js: راهنمای جامع و تخصصی کنترل نرخ درخواست‌ها

در دنیای مدرن توسعه APIها، یکی از مهمترین چالش‌ها، محافظت از سرورها در برابر ترافیک بیش از حد، حملات DoS (محرومیت از سرویس) و سوءاستفاده از منابع است. اینجاست که API Throttling در Node.js وارد می‌شود. API Throttling مکانیزمی است که تعداد درخواست‌های مجاز یک کلاینت را در یک بازه زمانی مشخص محدود می‌کند. اما API Throttling در Node.js دقیقاً چیست و چرا باید به آن توجه کنیم؟ در پاسخ باید گفت: API Throttling در Node.js به مجموعه تکنیک‌ها، الگوریتم‌ها و پیاده‌سازی‌هایی گفته می‌شود که با استفاده از قابلیت‌های غیرهمزمان (asynchronous) Node.js، نرخ درخواست‌های ورودی به APIها را کنترل می‌کنند. در این مقاله از دانا پدیا، قصد داریم به صورت عمیق و تخصصی به API Throttling در Node.js بپردازیم. از مفاهیم پایه و انواع الگوریتم‌های Throttling گرفته تا پیاده‌سازی عملی با Express، استفاده از Redis برای مقیاس‌پذیری، پیاده‌سازی در معماری میکروسرویس، استراتژی‌های مختلف (Rate Limit، Leaky Bucket، Token Bucket، Sliding Window)، مدیریت خطا، ارسال هدرهای مناسب، و بهترین شیوه‌ها را پوشش خواهیم داد.

اگر شما یک توسعه‌دهنده بک‌اند، معمار سیستم، یا مدیر فنی هستید که می‌خواهید APIهای خود را در برابر سوءاستفاده، حملات و ترافیک ناگهانی محافظت کنید، این مقاله از دانا پدیا دقیقاً برای شما نوشته شده است. API Throttling در Node.js یکی از الزامات هر API عمومی و حتی داخلی در مقیاس بزرگ است. پس با ما همراه باشید تا سفری حرفه‌ای در دنیای API Throttling در Node.js را آغاز کنیم.

Rust Web Framework Actix


API Throttling چیست و چرا اهمیت دارد؟

قبل از پرداختن به جزئیات API Throttling در Node.js، باید درک کنیم که Throttling چیست و چه مشکلی را حل می‌کند.

تعریف Throttling و Rate Limiting

API Throttling (محدودیت نرخ) فرآیند محدود کردن تعداد درخواست‌هایی است که یک کلاینت (کاربر، IP، API key) می‌تواند در یک بازه زمانی مشخص به سرور ارسال کند. دو مفهوم مرتبط اما متفاوت وجود دارد:

  • Rate Limiting: محدودیت سخت. مثلاً “حداکثر ۱۰۰ درخواست در دقیقه”. بعد از رسیدن به حد مجاز، درخواست‌های بعدی با خطای 429 Too Many Requests رد می‌شوند.
  • Throttling: معمولاً به معنای کاهش تدریجی نرخ یا به تعویق انداختن درخواست‌هاست (نه رد کامل). مثلاً “اگر از حد مجاز گذشتی، درخواست‌هایت را با تأخیر پردازش می‌کنم”.

در عمل، این دو واژه اغلب به جای یکدیگر استفاده می‌شوند. در این مقاله از دانا پدیا، تمرکز اصلی روی Rate Limiting (رد کردن درخواست‌های اضافی) است.

چرا API Throttling در Node.js مهم است؟

دلایل متعددی برای پیاده‌سازی API Throttling در Node.js وجود دارد:

۱. محافظت در برابر حملات DoS و Brute Force: مهاجم ممکن است هزاران درخواست در ثانیه به API شما ارسال کند تا سرور را از کار بیندازد (DoS) یا رمز عبور را حدس بزند (Brute Force). Throttling این حملات را خنثی می‌کند.

۲. عدالت در استفاده از منابع (Fair Usage): اگر یک کلاینت (مثلاً یک API key رایگان) ۱۰۰۰ درخواست در ثانیه ارسال کند، سایر کلاینت‌ها نمی‌توانند از API استفاده کنند. Throttling تضمین می‌کند همه کلاینت‌ها سهم عادلانه‌ای از منابع داشته باشند.

۳. کاهش هزینه‌های زیرساخت: هر درخواست به API هزینه‌هایی دارد (محاسبات، پهنای باند، دیتابیس). Throttling از مصرف بی‌رویه منابع جلوگیری می‌کند.

۴. پیشگیری از اشکالات (Bugs) و حلقه‌های بی‌نهایت: گاهی یک باگ در کلاینت (مثلاً حلقه بی‌نهایت) می‌تواند میلیون‌ها درخواست به سرور بفرستد. Throttling از این سناریوها محافظت می‌کند.

۵. مدیریت ترافیک پیک (Traffic Spikes): اگر ناگهان ترافیک API ۱۰ برابر شود (مثلاً به دلیل معرفی محصول جدید)، Throttling تضمین می‌کند سرور از کار نیفتد و همه کاربران (با تأخیر بیشتر) بتوانند از سرویس استفاده کنند.

۶. الزامات قانونی و SLA: بسیاری از APIهای تجاری (مانند Google، Twitter، GitHub) در قراردادهای خود محدودیت نرخ را مشخص می‌کنند. پیاده‌سازی API Throttling در Node.js به شما کمک می‌کند به این تعهدات عمل کنید.

۷. مدیریت کوئوتا (Quota) برای پلن‌های قیمت‌گذاری: اگر API پولی دارید، می‌توانید برای پلن‌های مختلف (رایگان، حرفه‌ای، سازمانی) محدودیت‌های متفاوتی اعمال کنید.

آمار و ارقام

  • بدون Throttling، یک سرور Node.js با ۴ هسته CPU می‌تواند تنها با ۵۰۰ درخواست در ثانیه از کار بیفتد (در صورت منطق سنگین). با Throttling هوشمند، می‌توانید همان سرور را در برابر ۵۰,۰۰۰ درخواست در ثانیه محافظت کنید.
  • ۴۰٪ از حملات DoS در سال ۲۰۲۳ از طریق اَب‌الگوهای ساده Rate Limiting قابل جلوگیری بودند.
  • APIهای بزرگ عمومی مانند GitHub، Stripe و Twitter از Throttling استفاده می‌کنند و هدرهای استانداردی مانند X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset را برمی‌گردانند.

تست Accessibility با Axe


الگوریتم‌های اصلی API Throttling

در API Throttling در Node.js، الگوریتم‌های مختلفی برای پیاده‌سازی محدودیت نرخ وجود دارد. هر کدام مزایا و معایب خاص خود را دارند.

۱. الگوریتم Fixed Window (پنجره ثابت)

ساده‌ترین الگوریتم. تعداد درخواست‌ها در یک بازه زمانی ثابت (مثلاً ۱ دقیقه) شمارش می‌شود.

[دقیقه 0 تا 60]: 100 درخواست مجاز
[دقیقه 60 تا 120]: 100 درخواست مجاز

مزایا:

  • بسیار ساده و سریع
  • مصرف حافظه کم (فقط یک شمارنده در هر کلید)

معایب:

  • مشکل “مرز پنجره” (Boundary Problem): کاربر می‌تواند در ثانیه ۵۹، ۱۰۰ درخواست و در ثانیه ۶۱، ۱۰۰ درخواست دیگر بفرستد (۲۰۰ درخواست در ۲ ثانیه!).

۲. الگوریتم Sliding Window (پنجره لغزنده)

مشکل Fixed Window را حل می‌کند. در این روش، پنجره به صورت پیوسته روی خط زمان حرکت می‌کند.

انواع:

  • Sliding Window Log: تمام زمان‌های هر درخواست ذخیره می‌شوند. دقیق اما پرمصرف.
  • Sliding Window Counter: ترکیبی از Fixed Window و تکنیک‌های آماری. دقیق‌تر از Fixed Window با حافظه کمتر از Log.

۳. الگوریتم Token Bucket (سطل توکن)

یکی از محبوب‌ترین الگوریتم‌ها برای API Throttling در Node.js. در این روش، سطلی وجود دارد که با نرخ ثابتی (مثلاً ۱۰ توکن در ثانیه) پر می‌شود. هر درخواست یک توکن مصرف می‌کند. اگر توکن نباشد، درخواست رد می‌شود.

مزایا:

  • اجازه می‌دهد نرخ لحظه‌ای افزایش یابد (Burst) تا ظرفیت سطل
  • پیاده‌سازی نسبتاً ساده با Redis
  • مناسب برای APIهایی که نرخ متوسط مهم است

۴. الگوریتم Leaky Bucket (سطل چکه‌کننده)

مشابه Token Bucket اما معکوس. درخواست‌ها به صف می‌روند و با نرخ ثابتی پردازش می‌شوند. اگر صف پر شود، درخواست‌ها رد می‌شوند.

مزایا:

  • نرخ خروجی کاملاً ثابت و قابل پیش‌بینی
  • مناسب برای پردازش دسته‌ای (batch processing)

معایب:

  • حافظه بیشتری نیاز دارد (برای نگهداری صف)
  • تأخیر (latency) متغیر است

مقایسه سریع الگوریتم‌ها

الگوریتمدقتمصرف حافظهپشتیبانی از Burstپیچیدگی
Fixed Windowکمبسیار کمخیربسیار ساده
Sliding Window Logبسیار بالازیادخیرمتوسط
Sliding Window Counterبالاکمخیرمتوسط
Token Bucketمتوسطکمبلهساده
Leaky Bucketبالامتوسطمحدودمتوسط

AsyncLocalStorage در Node.js

API Throttling در Node.js

پیاده‌سازی ساده API Throttling در Node.js (بدون کتابخانه)

بیایید با یک پیاده‌سازی اولیه از API Throttling در Node.js با Express شروع کنیم. این نسخه از حافظه (Memory) استفاده می‌کند و برای محیط توسعه یا تست مناسب است (در production از Redis استفاده خواهیم کرد).

مثال پایه: Fixed Window با حافظه محلی

// middleware/rateLimiter.js (نسخه ساده - فقط برای آموزش)
const rateLimitMap = new Map(); // کلید: IP یا userId, مقدار: { count, resetTime }

function rateLimiter(options = {}) {
  const windowMs = options.windowMs || 60 * 1000; // 1 دقیقه پیش‌فرض
  const maxRequests = options.maxRequests || 100; // 100 درخواست در هر پنجره

  return function (req, res, next) {
    const clientId = req.ip || req.headers['x-forwarded-for'] || req.connection.remoteAddress;
    const now = Date.now();

    if (!rateLimitMap.has(clientId)) {
      // اولین درخواست این کلاینت
      rateLimitMap.set(clientId, {
        count: 1,
        resetTime: now + windowMs
      });
      return next();
    }

    const clientData = rateLimitMap.get(clientId);

    if (now > clientData.resetTime) {
      // پنجره جدید شروع شده است
      clientData.count = 1;
      clientData.resetTime = now + windowMs;
      rateLimitMap.set(clientId, clientData);
      return next();
    }

    if (clientData.count < maxRequests) {
      // درخواست مجاز است
      clientData.count++;
      rateLimitMap.set(clientId, clientData);
      return next();
    }

    // محدودیت نرخ نقض شده است
    res.set('Retry-After', Math.ceil((clientData.resetTime - now) / 1000));
    res.status(429).json({
      error: 'Too Many Requests',
      message: `شما از حد مجاز ${maxRequests} درخواست در هر ${windowMs / 1000} ثانیه عبور کرده‌اید.`,
      retryAfter: Math.ceil((clientData.resetTime - now) / 1000)
    });
  };
}

module.exports = rateLimiter;

استفاده در Express

// app.js
const express = require('express');
const rateLimiter = require('./middleware/rateLimiter');

const app = express();

// اعمال Throttling روی همه مسیرها
app.use(rateLimiter({
  windowMs: 60 * 1000,  // 1 دقیقه
  maxRequests: 50       // حداکثر 50 درخواست
}));

// یا فقط روی مسیر خاص
app.post('/api/login', rateLimiter({ windowMs: 15 * 60 * 1000, maxRequests: 5 }), (req, res) => {
  // فقط ۵ بار در ۱۵ دقیقه می‌توان لاگین کرد (جلوگیری از Brute Force)
  res.json({ message: "ورود موفق" });
});

app.get('/api/data', (req, res) => {
  res.json({ data: "اطلاعات حساس" });
});

app.listen(3000, () => {
  console.log('سرور با **API Throttling در Node.js** در حال اجرا است');
});

محدودیت‌های پیاده‌سازی حافظه‌ای

این پیاده‌سازی برای پروژه‌های واقعی API Throttling در Node.js مناسب نیست:

  • اگر سرور ریستارت شود، همه شمارنده‌ها از بین می‌روند.
  • در محیط چند فرآیندی (مثل PM2 یا Kubernetes) هر فرآیند map جداگانه دارد.
  • برای میلیون‌ها کلاینت، مصرف حافظه زیاد می‌شود.

برای رفع این مشکلات، به Redis نیاز داریم.


پیاده‌سازی API Throttling در Node.js با Redis

Redis با دستورات اتمی و پشتیبانی از TTL، گزینه ایده‌آلی برای API Throttling در Node.js در مقیاس بزرگ است.

نصب Redis و کتابخانه ioredis

npm install ioredis express

پیاده‌سازی Token Bucket با Redis

// services/rateLimiterRedis.js
const Redis = require('ioredis');

class RedisRateLimiter {
  constructor(options = {}) {
    this.redis = new Redis({
      host: options.redisHost || 'localhost',
      port: options.redisPort || 6379,
      ...options.redisOptions
    });
    this.windowMs = options.windowMs || 60 * 1000;
    this.maxRequests = options.maxRequests || 100;
    this.keyPrefix = options.keyPrefix || 'rate_limit:';
  }

  // پیاده‌سازی با الگوریتم Fixed Window (با Lua Script برای اتمی بودن)
  async isAllowed(clientId) {
    const key = `${this.keyPrefix}${clientId}`;
    const now = Date.now();
    const windowStart = now - this.windowMs;

    // اسکریپت Lua برای عملیات اتمی (جلوگیری از race condition)
    const luaScript = `
      local key = KEYS[1]
      local now = tonumber(ARGV[1])
      local windowMs = tonumber(ARGV[2])
      local maxRequests = tonumber(ARGV[3])
      local windowStart = now - windowMs

      -- حذف درخواست‌های قدیمی
      redis.call('ZREMRANGEBYSCORE', key, 0, windowStart)

      -- تعداد درخواست‌های فعلی
      local currentCount = redis.call('ZCARD', key)

      if currentCount < maxRequests then
        -- اضافه کردن درخواست جدید
        redis.call('ZADD', key, now, now .. ':' .. math.random())
        redis.call('EXPIRE', key, math.ceil(windowMs / 1000))
        return {1, maxRequests - currentCount - 1, maxRequests}
      else
        -- دریافت قدیمی‌ترین درخواست برای محاسبه Retry-After
        local oldest = redis.call('ZRANGE', key, 0, 0, 'WITHSCORES')
        local retryAfter = 0
        if #oldest > 0 then
          retryAfter = math.ceil((tonumber(oldest[2]) + windowMs - now) / 1000)
        end
        return {0, 0, maxRequests, retryAfter}
      end
    `;

    const result = await this.redis.eval(
      luaScript,
      1,
      key,
      now,
      this.windowMs,
      this.maxRequests
    );

    const isAllowed = result[0] === 1;
    const remaining = result[1];
    const limit = result[2];
    const retryAfter = result[3] || 0;

    return { isAllowed, remaining, limit, retryAfter };
  }

  // پیاده‌سازی Token Bucket با Redis
  async isAllowedTokenBucket(clientId, options = {}) {
    const capacity = options.capacity || 100;       // حداکثر توکن‌ها در سطل
    const refillRate = options.refillRate || 10;    // توکن در ثانیه
    const key = `${this.keyPrefix}token:${clientId}`;

    const luaScript = `
      local key = KEYS[1]
      local capacity = tonumber(ARGV[1])
      local refillRate = tonumber(ARGV[2])
      local now = tonumber(ARGV[3])

      local bucket = redis.call('HMGET', key, 'tokens', 'lastRefill')
      local tokens = tonumber(bucket[1]) or capacity
      local lastRefill = tonumber(bucket[2]) or now

      -- محاسبه توکن‌های جدید (نرخ پر شدن)
      local elapsed = math.max(0, now - lastRefill)
      local newTokens = math.min(capacity, tokens + (elapsed * refillRate / 1000))

      if newTokens >= 1 then
        -- مصرف یک توکن
        local updatedTokens = newTokens - 1
        redis.call('HMSET', key, 'tokens', updatedTokens, 'lastRefill', now)
        redis.call('EXPIRE', key, math.ceil(capacity / refillRate) * 2)
        return {1, math.floor(updatedTokens), capacity}
      else
        -- بدون توکن، محاسبه زمان انتظار
        local waitTime = math.ceil((1 - newTokens) * 1000 / refillRate)
        return {0, 0, capacity, waitTime}
      end
    `;

    const result = await this.redis.eval(
      luaScript,
      1,
      key,
      capacity,
      refillRate,
      Date.now()
    );

    const isAllowed = result[0] === 1;
    const remaining = result[1];
    const limit = result[2];
    const waitTime = result[3] || 0;

    return { isAllowed, remaining, limit, waitTime };
  }
}

module.exports = RedisRateLimiter;

Middleware Express با Redis

// middleware/rateLimiterRedisMiddleware.js
const RedisRateLimiter = require('../services/rateLimiterRedis');

// استخراج شناسه کلاینت (می‌تواند IP، کاربر احراز هویت شده، یا API key باشد)
function getClientId(req) {
  // اولویت 1: کاربر احراز هویت شده
  if (req.user && req.user.id) {
    return `user:${req.user.id}`;
  }
  // اولویت 2: API key
  if (req.headers['x-api-key']) {
    return `apikey:${req.headers['x-api-key']}`;
  }
  // اولویت 3: IP (با احتساب proxy)
  const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
  return `ip:${ip}`;
}

function createRateLimiter(options = {}) {
  const limiter = new RedisRateLimiter({
    redisHost: process.env.REDIS_HOST || 'localhost',
    redisPort: process.env.REDIS_PORT || 6379,
    windowMs: options.windowMs || 60 * 1000,
    maxRequests: options.maxRequests || 100,
    keyPrefix: options.keyPrefix || 'rate_limit:'
  });

  const algorithm = options.algorithm || 'fixedWindow'; // 'fixedWindow', 'tokenBucket'

  return async function rateLimiterMiddleware(req, res, next) {
    const clientId = getClientId(req);

    let result;
    if (algorithm === 'tokenBucket') {
      result = await limiter.isAllowedTokenBucket(clientId, {
        capacity: options.capacity || 100,
        refillRate: options.refillRate || 10
      });
    } else {
      result = await limiter.isAllowed(clientId);
    }

    // افزودن هدرهای استاندارد Rate Limit
    res.set('X-RateLimit-Limit', result.limit);
    res.set('X-RateLimit-Remaining', result.remaining);

    if (!result.isAllowed) {
      res.set('Retry-After', result.retryAfter || result.waitTime);
      return res.status(429).json({
        error: 'Too Many Requests',
        message: `شما از حد مجاز ${result.limit} درخواست عبور کرده‌اید.`,
        retryAfter: result.retryAfter || result.waitTime,
        limit: result.limit,
        remaining: 0
      });
    }

    next();
  };
}

module.exports = createRateLimiter;

استفاده در اپلیکیشن Express

// app.js
const express = require('express');
const createRateLimiter = require('./middleware/rateLimiterRedisMiddleware');

const app = express();

// محدودیت سراسری: 1000 درخواست در دقیقه برای هر IP
app.use(createRateLimiter({
  windowMs: 60 * 1000,
  maxRequests: 1000,
  algorithm: 'fixedWindow'
}));

// محدودیت خاص برای لاگین (Token Bucket برای پذیرش burst)
app.post('/api/login', createRateLimiter({
  windowMs: 15 * 60 * 1000,  // 15 دقیقه
  maxRequests: 5,            // حداکثر 5 بار
  algorithm: 'tokenBucket',
  capacity: 5,
  refillRate: 1 / 180        // 1 توکن هر 3 دقیقه (5 توکن در 15 دقیقه)
}), (req, res) => {
  res.json({ message: "ورود موفق" });
});

// محدودیت برای کاربران ویژه (API key متفاوت)
app.get('/api/premium-data', createRateLimiter({
  windowMs: 60 * 1000,
  maxRequests: 5000,        // 5000 درخواست در دقیقه برای کاربران ویژه
  algorithm: 'tokenBucket',
  capacity: 5000,
  refillRate: 5000 / 60
}), (req, res) => {
  res.json({ data: "داده‌های ویژه" });
});

app.listen(3000);

استفاده از کتابخانه express-rate-limit برای API Throttling در Node.js

اگر نمی‌خواهید از ابتدا پیاده‌سازی کنید، کتابخانه express-rate-limit محبوب‌ترین گزینه برای API Throttling در Node.js است.

نصب

npm install express-rate-limit

استفاده پایه

const rateLimit = require('express-rate-limit');

// محدودیت عمومی
const generalLimiter = rateLimit({
  windowMs: 60 * 1000, // 1 دقیقه
  max: 100, // حداکثر 100 درخواست
  message: 'شما بیش از حد مجاز درخواست ارسال کرده‌اید.',
  standardHeaders: true, // برگرداندن هدرهای `RateLimit-*`
  legacyHeaders: false, // غیرفعال کردن هدرهای `X-RateLimit-*`
});

// محدودیت لاگین (سخت‌گیرانه‌تر)
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 دقیقه
  max: 5,
  skipSuccessfulRequests: true, // درخواست‌های موفق را نشمار (بهتر برای لاگین)
  message: 'تعداد تلاش‌های ناموفق بیش از حد مجاز است. لطفاً بعداً تلاش کنید.'
});

app.use('/api/', generalLimiter);
app.post('/api/login', loginLimiter, loginHandler);

یکپارچه‌سازی با Redis (برای محیط چند فرآیندی)

npm install express-rate-limit rate-limit-redis ioredis
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');

const redisClient = new Redis({
  host: process.env.REDIS_HOST || 'localhost',
  port: process.env.REDIS_PORT || 6379,
});

const limiter = rateLimit({
  store: new RedisStore({
    sendCommand: (...args) => redisClient.call(...args),
  }),
  windowMs: 60 * 1000,
  max: 100,
  standardHeaders: true,
});

app.use('/api/', limiter);

Hotwire Turbo چیست


API Throttling در معماری میکروسرویس‌ها

در معماری میکروسرویس، API Throttling در Node.js چالش‌های خاص خود را دارد:

  1. مقیاس‌پذیری: هر سرویس ممکن است روی چندین نمونه اجرا شود.
  2. هماهنگی بین سرویس‌ها: کلاینت ممکن است به چندین سرویس مختلف درخواست بدهد.
  3. قابلیت اطمینان: اگر Redis مرکزی از کار بیفتد، Throttling نباید کل سیستم را مختل کند.

راه‌حل: Redis Cluster + Circuit Breaker

// gateway/rateLimiterGateway.js
const Redis = require('ioredis');
const CircuitBreaker = require('opossum');

class DistributedRateLimiter {
  constructor() {
    this.redis = new Redis.Cluster([
      { host: 'redis-node1', port: 6379 },
      { host: 'redis-node2', port: 6379 },
      { host: 'redis-node3', port: 6379 }
    ]);

    // Circuit Breaker برای محافظت در برابر خرابی Redis
    this.breaker = new CircuitBreaker(this._checkRateLimit.bind(this), {
      timeout: 1000, // 1 ثانیه تایم‌اوت
      errorThresholdPercentage: 50,
      resetTimeout: 30000 // 30 ثانیه
    });

    this.breaker.fallback(() => {
      // در صورت خرابی Redis، درخواست را مجاز کن (fallback باز)
      return { isAllowed: true, reason: 'redis-down' };
    });
  }

  async _checkRateLimit(clientId, limit, windowMs) {
    const key = `rate:${clientId}`;
    const now = Date.now();
    const windowStart = now - windowMs;

    const multi = this.redis.multi();
    multi.zremrangebyscore(key, 0, windowStart);
    multi.zcard(key);
    multi.zadd(key, now, `${now}:${Math.random()}`);
    multi.expire(key, Math.ceil(windowMs / 1000));

    const results = await multi.exec();
    const currentCount = results[1][1];

    if (currentCount < limit) {
      return { isAllowed: true, remaining: limit - currentCount - 1 };
    }
    return { isAllowed: false, remaining: 0 };
  }

  async isAllowed(clientId, limit, windowMs) {
    try {
      return await this.breaker.fire(clientId, limit, windowMs);
    } catch (error) {
      console.error('Rate limiter error:', error);
      return { isAllowed: true, remaining: limit }; // در صورت خطا، اجازه بده
    }
  }
}

کار با Prisma در Next.js


مدیریت خطا و پاسخ‌های مناسب در API Throttling

هنگامی که API Throttling در Node.js درخواستی را رد می‌کند، باید پاسخ‌های استاندارد و مفید برگرداند.

هدرهای استاندارد Rate Limit

طبق RFC 6585، APIها باید هدرهای زیر را برگردانند:

  • RateLimit-Limit: حداکثر تعداد درخواست مجاز در پنجره
  • RateLimit-Remaining: تعداد درخواست باقی‌مانده در پنجره فعلی
  • RateLimit-Reset: زمان باقی‌مانده تا بازنشانی پنجره (به ثانیه)

علاوه بر این، هدر Retry-After برای خطای ۴۲۹ ضروری است.

مثال پاسخ خطا

// پاسخ برای خطای 429 Too Many Requests
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "شما از حد مجاز درخواست عبور کرده‌اید.",
    "details": {
      "limit": 100,
      "remaining": 0,
      "resetInSeconds": 45,
      "retryAfter": 45
    }
  }
}

Middleware خطایابی پیشرفته

function rateLimitErrorHandler(err, req, res, next) {
  if (err instanceof RateLimitExceededError) {
    return res.status(429).json({
      error: {
        code: 'RATE_LIMIT_EXCEEDED',
        message: err.message,
        details: {
          limit: err.limit,
          remaining: err.remaining,
          resetInSeconds: err.resetTime,
          retryAfter: err.retryAfter
        }
      }
    });
  }
  next(err);
}

Redis Caching Patterns


استراتژی‌های پیشرفته API Throttling در Node.js

۱. Throttling لایه‌ای (Multi-layer Throttling)

اعمال محدودیت در چندین سطح: سراسری، به ازای IP، به ازای کاربر، به ازای API key، به ازای endpoint.

// محدودیت لایه‌ای
const globalLimiter = rateLimit({ windowMs: 60 * 1000, max: 10000 }); // کل API
const ipLimiter = rateLimit({ windowMs: 60 * 1000, max: 1000, keyGenerator: (req) => req.ip });
const userLimiter = rateLimit({ windowMs: 60 * 1000, max: 500, keyGenerator: (req) => req.user.id });
const endpointLimiter = rateLimit({ windowMs: 60 * 1000, max: 100, keyGenerator: (req) => `${req.user.id}:${req.path}` });

app.use(globalLimiter);
app.use(ipLimiter);
app.use(userLimiter);
app.use('/api/search', endpointLimiter);

۲. Throttling پویا بر اساس منابع (Resource-based Throttling)

برخی از endpointها سنگین‌تر هستند و باید محدودیت کمتری داشته باشند.

const resourceLimits = {
  '/api/search': { max: 50, windowMs: 60 * 1000 },     // سنگین
  '/api/profile': { max: 500, windowMs: 60 * 1000 },    // سبک
  '/api/analytics': { max: 10, windowMs: 60 * 1000 }    // بسیار سنگین
};

app.use((req, res, next) => {
  const limit = resourceLimits[req.path];
  if (limit) {
    return createRateLimiter(limit)(req, res, next);
  }
  next();
});

۳. Throttling با نرخ متغیر بر اساس زمان (Time-based Throttling)

در ساعات پیک، محدودیت را کاهش دهید.

function getDynamicLimit() {
  const hour = new Date().getHours();
  if (hour >= 9 && hour <= 17) {
    return 50; // ساعات کاری: محدودیت سخت‌گیرانه
  }
  return 500; // خارج از ساعات کاری: محدودیت宽松
}

const limiter = rateLimit({
  windowMs: 60 * 1000,
  max: (req) => getDynamicLimit(),
});

۴. Throttling با اولویت (Priority-based Throttling)

کاربران ویژه (Premium) محدودیت بالاتری دارند.

const limiter = rateLimit({
  windowMs: 60 * 1000,
  max: (req) => {
    if (req.user && req.user.plan === 'premium') return 10000;
    if (req.user && req.user.plan === 'pro') return 1000;
    return 100; // رایگان
  },
  skip: (req) => req.user && req.user.plan === 'enterprise' // بدون محدودیت
});

CSS :where() و :is() Selectors

API Throttling در Node.js

بهترین شیوه‌های API Throttling در Node.js

بر اساس تجربیات تیم دانا پدیا، رعایت این نکات شما را در API Throttling در Node.js به یک حرفه‌ای تبدیل می‌کند:

۱. از Redis برای ذخیره‌سازی توزیع‌شده استفاده کنید

همیشه در محیط production از Redis (یا مشابه) استفاده کنید. ذخیره‌سازی در حافظه (In-memory) فقط برای توسعه و تست مناسب است.

۲. هدرهای استاندارد را برگردانید

کلاینت‌های API انتظار هدرهای RateLimit-* و Retry-After را دارند. رعایت این استانداردها تجربه بهتری برای توسعه‌دهندگان API شما ایجاد می‌کند.

۳. از الگوریتم مناسب برای مورد استفاده انتخاب کنید

  • Fixed Window: برای سناریوهای ساده و غیرحساس
  • Sliding Window: برای نیاز به دقت بالا (مثلاً APIهای مالی)
  • Token Bucket: برای APIهایی که نیاز به Burst موقتی دارند (مثلاً آپلود فایل)
  • Leaky Bucket: برای پردازش دسته‌ای و صف‌ها

۴. محدودیت‌های متفاوت برای endpointهای مختلف

همیشه یک محدودیت سراسری برای کل API و محدودیت‌های اختصاصی برای endpointهای حساس (لاگین، ثبت‌نام، جستجو) تعریف کنید.

۵. از X-Forwarded-For به درستی استفاده کنید

اگر API شما پشت proxy (مثل Nginx) است، از req.headers['x-forwarded-for'] برای شناسایی IP واقعی کلاینت استفاده کنید.

۶. بازه‌های زمانی منطقی انتخاب کنید

  • لاگین: ۵ بار در ۱۵ دقیقه
  • ثبت‌نام: ۳ بار در ساعت
  • بازیابی رمز عبور: ۳ بار در روز
  • API عمومی: ۱۰۰ بار در دقیقه
  • API پولی: بسته به پلن (۱۰۰۰ تا ۱۰۰,۰۰۰ در دقیقه)

۷. از Circuit Breaker برای Resilience استفاده کنید

اگر Redis از کار افتاد، API شما نباید از کار بیفتد. در این حالت، یا Throttling را غیرفعال کنید (fallback باز) یا با حافظه محلی ادامه دهید (degraded mode).

۸. مانیتورینگ و هشدار (Alerting)

متریک‌های زیر را مانیتور کنید:

  • تعداد درخواست‌های رد شده (429) در دقیقه
  • کلاینت‌هایی که بیشترین نقض را دارند
  • زمان پاسخگویی Redis

۹. تست با بار (Load Testing)

قبل از استقرار، API خود را با ابزارهایی مانند Artillery یا k6 تست کنید تا مطمئن شوید Throttling به درستی کار می‌کند.

۱۰. مستندسازی برای توسعه‌دهندگان API

در مستندات API خود، محدودیت‌های نرخ را به وضوح ذکر کنید و مثال بزنید که در صورت رسیدن به محدودیت، چه پاسخی دریافت می‌کنند.

Storybook برای React


عیب‌یابی مشکلات رایج در API Throttling در Node.js

مشکل ۱: تخطی از محدودیت حتی با تعداد درخواست کم

علت: احتمالاً الگوریتم Fixed Window استفاده شده و مشکل “مرز پنجره” رخ می‌دهد.

راه‌حل: به Sliding Window یا Token Bucket مهاجرت کنید.

مشکل ۲: Redis به عنوان گلوگاه (Bottleneck) تبدیل شده است

علت: هر درخواست به Redis رفت و برگشت دارد (latency اضافی).

راه‌حل: از Redis Cluster استفاده کنید، یا درخواست‌ها را به صورت batch پردازش کنید (مثلاً هر ۱۰۰ میلی‌ثانیه یک بار).

مشکل ۳: Throttling در محیط چند فرآیندی کار نمی‌کند

علت: استفاده از Map معمولی در حافظه.

راه‌حل: از Redis یا store مشترک دیگر استفاده کنید.

مشکل ۴: کاربران بی‌گناه به دلیل اشتراک IP محدود می‌شوند

علت: کاربران پشت NAT یا VPN همگی یک IP مشترک دارند.

راه‌حل: از شناسه‌های دقیق‌تر (مانند JWT، API key، cookie) به جای IP استفاده کنید.

مشکل ۵: حملات DoS با تغییر IP (IP Spoofing)

علت: مهاجم می‌تواند هر بار IP خود را تغییر دهد و از محدودیت IP-based عبور کند.

راه‌حل: ترکیب محدودیت IP-based و user-based (برای کاربران احراز هویت شده).

Kubernetes Service Mesh


ابزارها و کتابخانه‌های مفید برای API Throttling در Node.js

  • express-rate-limit: محبوب‌ترین کتابخانه برای Express
  • rate-limit-redis: Store برای express-rate-limit با Redis
  • express-brute: کتابخانه قدیمی اما قدرتمند با پشتیبانی از Redis
  • bottleneck: کتابخانه پیشرفته برای Throttling در سمت کلاینت و سرور
  • bull: صف قدرتمند با قابلیت Throttling داخلی
  • opossum: Circuit Breaker برای resilience
  • فرق بین SQL و PL/SQL

نتیجه‌گیری نهایی از دیدگاه دانا پدیا

در این مقاله جامع از دانا پدیا، ما به طور کامل API Throttling در Node.js را از جنبه‌های مختلف بررسی کردیم. API Throttling در Node.js یک ضرورت برای هر API جدی است که می‌خواهد:

  • در برابر حملات DoS و Brute Force محافظت شود.
  • منابع خود را به طور عادلانه بین کاربران توزیع کند.
  • از خرابی‌های ناشی از ترافیک ناگهانی جلوگیری کند.
  • هزینه‌های زیرساخت را کنترل کند.
  • به تعهدات SLA خود عمل کند.

با استفاده از الگوریتم‌های مناسب (Token Bucket، Sliding Window)، ذخیره‌سازی توزیع‌شده (Redis)، و کتابخانه‌های استاندارد (express-rate-limit)، می‌توانید API Throttling در Node.js را به صورت مقیاس‌پذیر و کارآمد پیاده‌سازی کنید.

امیدواریم این مقاله از دانا پدیا برای شما مفید بوده باشد. اگر تجربه یا سؤالی درباره API Throttling در Node.js دارید، در بخش نظرات با ما در میان بگذارید.

پایتون Type Hints


سوالات متداول (FAQ)

۱. تفاوت بین Throttling و Rate Limiting چیست؟

در عمل، این دو واژه اغلب به جای یکدیگر استفاده می‌شوند. اما تفاوت ظریف: Rate Limiting معمولاً به رد کردن درخواست‌های اضافی اشاره دارد، در حالی که Throttling می‌تواند شامل تأخیر انداختن (delay) درخواست‌ها نیز باشد. در API Throttling در Node.js، تمرکز اصلی روی رد کردن درخواست‌های بیش از حد مجاز است.

۲. آیا می‌توان Throttling را بدون Redis پیاده‌سازی کرد؟

بله، برای پروژه‌های کوچک یا محیط توسعه می‌توانید از حافظه محلی (Map) استفاده کنید. اما برای production و محیط چند فرآیندی، Redis ضروری است. بدون Redis، هر فرآیند شمارنده جداگانه دارد و یک کاربر می‌تواند با ارسال درخواست به فرآیندهای مختلف از محدودیت عبور کند.

۳. بهترین الگوریتم برای API عمومی چیست؟

بستگی به نیاز شما دارد:

  • Token Bucket: محبوب‌ترین برای APIهای عمومی (اجازه Burst می‌دهد)
  • Sliding Window Counter: دقت بالا بدون مصرف حافظه زیاد
  • Fixed Window: فقط برای سناریوهای بسیار ساده

توصیه دانا پدیا: از Token Bucket با Redis استفاده کنید.

۴. چگونه محدودیت را برای کاربران ویژه (Premium) افزایش دهم؟

می‌توانید در middleware، سطح کاربر را بررسی کنید و max را پویا تنظیم کنید:

max: (req) => {
  if (req.user?.plan === 'premium') return 10000;
  if (req.user?.plan === 'pro') return 1000;
  return 100;
}

۵. آیا Throttling روی وب‌سوکت (WebSocket) هم کار می‌کند؟

بله، اما پیاده‌سازی متفاوت است. برای WebSocket باید تعداد پیام‌های دریافتی در هر کانکشن را محدود کنید. می‌توانید از کتابخانه ws با یک شمارنده ساده در هر connection استفاده کنید یا از Redis برای ارتباط بین چندین سرور WebSocket استفاده کنید.

۶. چه هدرهایی باید در پاسخ خطای ۴۲۹ برگردانم؟

طبق RFC 6585:

  • Retry-After: زمان انتظار به ثانیه
  • هدرهای استاندارد: RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset
  • همچنین می‌توانید از هدرهای قدیمی X-RateLimit-* برای سازگاری با کلاینت‌های قدیمی استفاده کنید.

۷. چگونه Throttling را در API Gateway (مانند Kong، Nginx، AWS API Gateway) پیاده‌سازی کنم؟

بهترین روش این است که Throttling را در لایه Gateway انجام دهید تا بار از روی سرورهای اپلیکیشن برداشته شود. مثلاً در Nginx:

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
location /api/ {
    limit_req zone=mylimit burst=20 nodelay;
    proxy_pass http://backend;
}

اما در API Throttling در Node.js که تمرکز مقاله است، پیاده‌سازی در خود اپلیکیشن Node.js انعطاف بیشتری دارد (مثلاً محدودیت بر اساس کاربر و endpoint).

۸. آیا Throttling تنها راه محافظت از API است؟

خیر، Throttling فقط یکی از لایه‌های دفاعی است. توصیه می‌شود از ترکیب موارد زیر استفاده کنید:

  • Throttling (محدودیت نرخ)
  • Authentication و Authorization (احراز هویت قوی)
  • Input Validation (اعتبارسنجی ورودی)
  • Web Application Firewall (WAF)
  • Rate Limiting در لایه CDN (مانند Cloudflare)

Node.js Memory Leak Detection


دیدگاهتان را بنویسید