req-guard-lite: Минималистичный лимитер запросов на основе TypeScript для Express
Узнайте, как работает легкий ограничитель скорости Express без зависимостей, от стандартных настроек в памяти до масштабирования с использованием Redis и пользовательских генераторов ключей.
Каждое приложение типа Express рано или поздно сталкивается с необходимостью ограничения частоты запросов.
Будь то защита маршрутов входа, сокращение количества спам-запросов или просто предотвращение непреднамеренного чрезмерного использования, ограничение входящего трафика быстро превращается из удобной опции в обязательное требование, как только API становится доступным для публики.
В процессе поиска решения для ограничения частоты запросов, соответствующего распространённым потребностям, стало ясно, что, хотя существует множество качественных библиотек, многие проекты на самом деле нуждаются в чем-то небольшом, простом для понимания и легком в настройке.
Именно этот пробел побудил создать req-guard-lite.
Зачем ещё один инструмент ограничения частоты запросов?
Большинству API не нужен полный корпоративный набор средств безопасности с самого начала.
Часто всё, что нужно, — это возможность написать что-то вроде:
app.use(rateLimit({
max: 100,
windowMs: 15 * 60 * 1000
}));
...и вернуться к разработке остальной части приложения.
Цели проектирования этого пакета были следующими:
- Сохранять легкость
- Быть простым в настройке
- С самого начала создаваться с учетом TypeScript
- Позволять легкое расширение
- Работать как с небольшими проектами, так и с крупными системами
Представляем req-guard-lite
req-guard-lite — это компактный мидлвэр для Express, предназначенный для защиты вашего API от огромного количества запросов.
По умолчанию он работает полностью в памяти, но также может масштабироваться до распределенных конфигураций путем интеграции с Redis.
Его функционал намеренно ограничен — он хорошо справляется с одной задачей:
Отслеживает входящие запросы и отклоняет клиентов, как только они превышают установленный лимит.
Особенности
Легкая реализация Отсутствие зависимостей во время выполнения в основном пакете Работает как мидлвэр для Express Поддержка TypeScript из коробки Возможна интеграция с Redis Поддержка пользовательских бэкендов хранения Поддержка пользовательских генераторов ключей
Начало работы
Установите его в качестве дополнения к Express.
npm install req-guard-lite express
Далее подключите его к своему приложению.
import express from 'express';
import { rateLimit } from 'req-guard-lite';
const app = express();
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: 'Too many requests, please try again later.'
});
app.use(limiter);
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000);
Вот и вся настройка.
Теперь ваш API блокирует любого клиента, отправляющего более 100 запросов в течение 15 минут.
Стандартное хранилище в памяти
По умолчанию счетчики запросов находятся в памяти.
На практике это означает:
- Не требуется Redis
- Не требуется база данных
- Не нужна дополнительная настройка
- Идеально для локальной разработки
- Хороший выбор для производства на одном сервере
Для большинства приложений этого достаточно для реализации ограничения скорости запросов.
Масштабирование с Redis
Когда приложение начинает работать на нескольких серверах или контейнерах, этим инстансам необходим общий способ отслеживания количества запросов.
Именно в этом и заключается роль Redis.
import Redis from "ioredis";
import { createRedisStore } from "req-guard-lite/redis";
const redis = new Redis();
const limiter = rateLimit({
max: 100,
windowMs: 15 * 60 * 1000,
store: createRedisStore(redis, {
max: 100,
windowMs: 15 * 60 * 1000
})
});
Благодаря этому каждая серверная инстанс читает и записывает одни и те же счетчики, поэтому ограничения сохраняются неизменными независимо от того, какой узел обрабатывает запрос.
Собственные генераторы ключей
Ограничение скорости по IP-адресу не всегда является правильным подходом.
В некоторых случаях предпочтительнее ограничивать запросы по:
- Идентификатору пользователя
- Ключу API
- Идентификатору арендатора
- Организации
- Полю subject в JWT
- Либо любому другому идентификатору, соответствующему вашей модели
Чтобы это обеспечить, req-guard-lite позволяет использовать собственную функцию генерации ключей.
const limiter = rateLimit({
max: 100,
keyGenerator: (req) =>
req.headers["x-api-key"] as string
});
Или же использовать в качестве ключа информацию о залогиненном пользователе:
const limiter = rateLimit({
max: 50,
keyGenerator: (req) =>
(req as any).user.id
});
Сам мидлвэр не зависит от того, что представляет собой ключ — он просто подсчитывает количество вызовов на основе любого идентификатора, возвращаемого вашей функцией.
Используйте собственный хранилище
Возможность расширения была одним из ключевых требований с самого начала.
Вместо принудительного использования Redis, req-guard-lite предоставляет простой интерфейс RateLimitStore. Если ваша инфраструктура уже использует:
- PostgreSQL
- DynamoDB
- Memcached
- MongoDB
- SQLite
- Какой-либо другой пользовательский слой кэширования
вы можете интегрировать его, реализовав этот единственный интерфейс.
class MyStore implements RateLimitStore {
consume(key: string) {
// your implementation
}
}
Такая архитектура позволяет пакету быть адаптируемым к практически любому бэкенду, который уже используется.
Важный совет для производства
Если ваше приложение работает за:
- Nginx
- Балансировщиком нагрузки AWS
- Heroku
- Cloudflare
- Любым обратным прокси
обязательно правильно настройте Express:
app.set("trust proxy", 1);
Если пропустить этот шаг, Express будет часто рассматривать каждый входящий запрос как исходящий от самого прокси, в результате чего все пользователи будут использовать один и тот же лимит количества запросов. Это можно исправить одной строкой кода, но это предотвращает проблемы в производственной среде, которые застают многие команды врасплох.
Как это работает
Внутренний процесс преднамеренно минимален:
- Приходит запрос.
- Мидлвэр создает для него ключ (по умолчанию — IP-адрес клиента).
- Активное хранилище увеличивает счетчик, связанный с этим ключом.
Поскольку хранилище является модульным, этот же алгоритм работы соблюдается независимо от того, используется ли память, Redis или пользовательская реализация.
Почему TypeScript?
Вся библиотека написана на TypeScript, что обеспечивает:
- Строгую типизацию на всех уровнях
- Улучшенное автодополнение в редакторе
- Упрощённое долгосрочное обслуживание
- API, которые сложнее неправильно использовать
Пользователи TypeScript получают полные определения типов сразу, без необходимости устанавливать дополнительные пакеты @types.
План развития
Разработка продолжается, и уже запланировано несколько новых функций.
v0.4.0
- Поддержка стандартных заголовков ответа для ограничения частоты запросов
X-RateLimit-Limit
X-RateLimit-Remaining
X-RateLimit-Reset
Эти заголовки позволяют клиентским приложениям видеть, сколько запросов осталось до достижения лимита.
v0.5.0
Настроимые хуки, которые активируются при превышении лимита, полезные для таких задач, как:
- Логирование
- Сбор метрик
- Уведомления
- Аналитика
- Отправка данных во внешние инструменты мониторинга
Почему это open source
Этот проект не является реакцией на недостатки существующих библиотек — в экосистеме Node уже существует несколько отличных решений для ограничения частоты запросов. req-guard-lite был создан потому, что целью был пакет, который:
- Достаточно компактен, чтобы его можно было прочитать и понять за один просмотр
- Прост в расширении
- Разработан с упором на TypeScript
- Не содержит ненужной сложности
- Достаточно гибок, чтобы масштабироваться в соответствии с реальными потребностями производства
Разработка этого проекта также стала ценной практикой в области публикации пакетов, проектирования API, написания тестов, интеграции с Redis и создания абстракций, удобных для использования другими разработчиками.
Открытие проекта с открытым исходным кодом — один из самых эффективных способов совершенствования инженерных навыков. Как только другие разработчики могут устанавливать, использовать и вносить изменения в ваш код, вы вынуждены думать не только о своих непосредственных задачах — документация, проектирование API, тестирование, версионирование и обратная совместимость становятся реальными ограничениями, с которыми необходимо считаться при проектировании.
req-guard-lite начинался как небольшой мидлвэр, созданный для решения личной проблемы, но есть надежда, что он превратится в полезный, легкий и расширяемый инструмент ограничения частоты запросов для других разработчиков Express. Мы приветствуем любые отзывы, предложения по функциям и вклады.