Для контроля нагрузки на API используются несколько стратегий ограничения запросов:
const requestCounts = new Map();
app.use('/api', (req, res, next) => {
const ip = req.ip;
const currentWindow = Math.floor(Date.now() / 60000); // 1 минута
const key = `${ip}:${currentWindow}`;
requestCounts.set(key, (requestCounts.get(key) || 0) + 1);
if (requestCounts.get(key) > 100) { // Лимит 100 запросов в минуту
return res.status(429).send('Too Many Requests');
}
next();
});
Более точный, но сложнее в реализации:
const redis = require('redis');
const client = redis.createClient();
app.use('/api', async (req, res, next) => {
const ip = req.ip;
const now = Date.now();
const windowSize = 60000; // 1 минута
const result = await client.multi()
.zRemRangeByScore(ip, 0, now - windowSize)
.zCard(ip)
.zAdd(ip, { score: now, value: now.toString() })
.expire(ip, windowSize / 1000)
.exec();
if (result[1] > 100) {
return res.status(429).send('Too Many Requests');
}
next();
});
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 минут
max: 100, // Лимит 100 запросов с одного IP
standardHeaders: true,
legacyHeaders: false,
});
app.use('/api', limiter);
Для Fastify приложений:
const fastify = require('fastify')();
const fastifyRateLimit = require('fastify-rate-limit');
fastify.register(fastifyRateLimit, {
max: 100,
timeWindow: '1 minute'
});
Для кластеров и микросервисов используйте Redis:
const { RateLimiterRedis } = require('rate-limiter-flexible');
const rateLimiter = new RateLimiterRedis({
storeClient: redisClient,
points: 10, // 10 запросов
duration: 1, // за 1 секунду
});
app.use(async (req, res, next) => {
try {
await rateLimiter.consume(req.ip);
next();
} catch (rejRes) {
res.status(429).send('Too Many Requests');
}
});
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100
});
const authLimiter = rateLimit({
windowMs: 60 * 60 * 1000,
max: 5
});
app.use('/api/', apiLimiter);
app.use('/auth/', authLimiter);
app.use((req, res, next) => {
if (req.user?.isPremium) {
return next(); // Пропускаем лимиты для премиум пользователей
}
limiter(req, res, next);
});
Более гибкий подход для burst-трафика:
class TokenBucket {
constructor(capacity, refillRate) {
this.capacity = capacity;
this.tokens = capacity;
this.lastRefill = Date.now();
this.refillRate = refillRate; // tokens/ms
}
consume(tokens = 1) {
this.refill();
if (this.tokens >= tokens) {
this.tokens -= tokens;
return true;
}
return false;
}
refill() {
const now = Date.now();
const elapsed = now - this.lastRefill;
this.tokens = Math.min(
this.capacity,
this.tokens + elapsed * this.refillRate
);
this.lastRefill = now;
}
}
const bucket = new TokenBucket(10, 0.001); // 10 токенов, 1 токен/сек
Правильные заголовки помогают клиентам:
app.use((req, res, next) => {
res.set({
'X-RateLimit-Limit': '100',
'X-RateLimit-Remaining': remainingRequests,
'X-RateLimit-Reset': Math.ceil(resetTime / 1000)
});
next();
});
Дополнительные меры для серьезной защиты:
Выбор реализации зависит от: