Главная / Статьи / req-guard-lite: Минималистичный лимитер запросов на основе TypeScript для Express

req-guard-lite: Минималистичный лимитер запросов на основе TypeScript для Express

Узнайте, как работает легкий ограничитель скорости Express без зависимостей, от стандартных настроек в памяти до масштабирования с использованием Redis и пользовательских генераторов ключей.

1291 слов

Каждое приложение типа 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 будет часто рассматривать каждый входящий запрос как исходящий от самого прокси, в результате чего все пользователи будут использовать один и тот же лимит количества запросов. Это можно исправить одной строкой кода, но это предотвращает проблемы в производственной среде, которые застают многие команды врасплох.

Как это работает

Внутренний процесс преднамеренно минимален:

  1. Приходит запрос.
  2. Мидлвэр создает для него ключ (по умолчанию — IP-адрес клиента).
  3. Активное хранилище увеличивает счетчик, связанный с этим ключом.
  • Как только счётчик превысит установленный порог, промежуточное приложение ответит HTTP-статусом 429 Too Many Requests.
  • Если лимит ещё не достигнут, запрос пройдёт без изменений.
  • Поскольку хранилище является модульным, этот же алгоритм работы соблюдается независимо от того, используется ли память, 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. Мы приветствуем любые отзывы, предложения по функциям и вклады.