Ограничитель частоты

Webman Ограничитель частоты, поддерживающий аннотированное ограничение частоты.
Поддерживает драйверы apcu, redis, memory.

Адрес исходного кода

https://github.com/webman-php/rate-limiter

Установка

composer require webman/rate-limiter

Использование

<?php
namespace app\controller;

use RuntimeException;
use Webman\RateLimiter\Annotation\RateLimiter;

class UserController
{

    #[RateLimiter(limit: 10)]
    public function index(): string
    {
        // По умолчанию ограничение частоты по IP, единица времени по умолчанию 1 секунда
        return 'Не более 10 запросов с каждого IP в секунду';
    }

    #[RateLimiter(limit: 100, ttl: 60, key: RateLimiter::UID)]
    public function search(): string
    {
        // key: RateLimiter::UID, ограничение по частоте на основе ID пользователя, требует, чтобы session('user.id') не была пустой
        return 'Не более 100 поисков для каждого пользователя за 60 секунд';
    }

    #[RateLimiter(limit: 1, ttl: 60, key: RateLimiter::SID, message: 'Каждый человек может отправить только 1 письмо в минуту')]
    public function sendMail(): string
    {
        // key: RateLimiter::SID, ограничение по частоте на основе session_id
        return 'Письмо успешно отправлено';
    }

    #[RateLimiter(limit: 100, ttl: 24*60*60, key: 'coupon', message: 'Сегодня все купоны уже выданы, приходите завтра')]
    #[RateLimiter(limit: 1, ttl: 24*60*60, key: RateLimiter::UID, message: 'Каждый пользователь может получить купон только один раз в день')]
    public function coupon(): string
    {
        // key: 'coupon', здесь coupon - это пользовательский ключ, глобально ограничение частоты по ключу coupon, не более 100 купонов в день
        // Одновременно ограничение по пользователям, каждый пользователь может получить купон только один раз в день
        return 'Купон успешно выдан';
    }

    #[RateLimiter(limit: 5, ttl: 24*60*60, key: [UserController::class, 'getMobile'], message: 'Не более 5 SMS с каждого номера телефона в день')]
    public function sendSms2(): string
    {
        // Когда ключ является переменной, можно использовать способ [класс, статический метод] для получения ключа, например [UserController::class, 'getMobile'] вызовет метод getMobile() класса UserController для получения ключа
        return 'SMS успешно отправлено';
    }

    /**
     * Пользовательский ключ, получение номера телефона, должен быть статическим методом
     * @return string
     */
    public static function getMobile(): string
    {
        return request()->get('mobile');
    }

    #[RateLimiter(limit: 1, ttl: 10, key: RateLimiter::IP, message: 'Частота ограничена', exception: RuntimeException::class)]
    public function testException(): string
    {
        // При превышении лимита, по умолчанию возбуждается исключение Webman\RateLimiter\RateLimitException, можно изменить через параметр exception
        return 'ok';
    }

}

Пояснение

  • По умолчанию единица времени составляет 1 секунду
  • Можно установить единицу времени с помощью ttl, например ttl:60 это 60 секунд
  • По умолчанию ограничение частоты по IP (по умолчанию 127.0.0.1 не ограничивается, см. раздел конфигурации ниже)
  • Встроенное ограничение по IP, UID (требует, чтобы session('user.id') не была пустой) и SID (ограничение по session_id)
  • Если используется прокси nginx, для ограничения по IP необходимо передавать заголовок X-Forwarded-For, см. nginx прокси
  • При превышении лимита будет возбуждено исключение Webman\RateLimiter\RateLimitException, можно использовать exception:xx для определения пользовательского класса исключения
  • При возбуждении исключения, сообщение об ошибке по умолчанию Too Many Requests, можно задать пользовательское сообщение через message:xx
  • Сообщение об ошибке по умолчанию также можно изменить с помощью многоязычности, в Linux воспользуйтесь следующими командами
    composer require symfony/translation
    mkdir resource/translations/zh_CN/ -p
    echo "<?php
    return [
    'Too Many Requests' => 'Частота запросов ограничена'
    ];" > resource/translations/zh_CN/messages.php
    php start.php restart

Интерфейс

Иногда разработчики хотят напрямую вызывать ограничитель частоты в коде, смотрите следующий код

<?php
namespace app\controller;

use RuntimeException;
use Webman\RateLimiter\Limiter;

class UserController {

    public function sendSms(string $mobile): string
    {
        // Здесь mobile выступает как ключ
        Limiter::check($mobile, 5, 24*60*60, 'Не более 5 SMS с каждого номера телефона в день');
        return 'SMS успешно отправлено';
    }
}

Настройка

config/plugin/webman/rate-limiter/app.php

<?php
return [
    'enable' => true,
    'driver' => 'auto', // auto, apcu, memory, redis
    'stores' => [
        'redis' => [
            'connection' => 'default',
        ]
    ],
    // Запросы с этих IP не подлежат ограничениям по частоте (эффективно только при ключе RateLimiter::IP)
    'ip_whitelist' => [
        '127.0.0.1',
    ],
];
  • enable: Включить ли ограничение частоты
  • driver: Одно из значений auto, apcu, memory, redis, при использовании auto будет автоматически выбран один из apcu или memory
  • stores: Конфигурация redis, connection соответствует ключу в config/redis.php
  • ip_whitelist: IP-адреса из белого списка не будут подлежать ограничению частоты (эффективно только при ключе RateLimiter::IP)

Выбор драйвера

memory

  • Описание
    Не требует установки дополнительных расширений, лучшая производительность.

  • Ограничения использования
    Ограничение частоты действует только в рамках текущего процесса, данные по ограничению не разделяются между несколькими процессами, также не поддерживает кластерное ограничение.

  • Подходящие сценарии
    Среда разработки Windows; бизнес, который не требует строгого ограничения частоты; защита от CC-атак.

apcu

  • Установка расширения
    Необходимо установить расширение apcu и настроить в php.ini

    apc.enabled=1
    apc.enable_cli=1

    Если не знаете, где находится php.ini, можно найти его с помощью команды php --ini

  • Описание
    Производительность немного ниже, чем у memory; поддерживает совместное использование данных по ограничению частоты между несколькими процессами.

  • Ограничения использования
    Не поддерживает кластерные системы

  • Подходящие сценарии
    Любая среда разработки; ограничение частоты на одной машине в продакшене; сценарии, где кластеру не нужно строгое ограничение частоты; защита от CC-атак.

redis

  • Зависимости
    Необходимо установить расширение redis и компонент Redis, команда установки

    composer require -W illuminate/redis illuminate/events
  • Описание
    Производительность ниже, чем у apcu; поддерживает точное ограничение частоты как на одной машине, так и в кластере.

  • Подходящие сценарии
    Среда разработки; однопроцессная среда; кластерная среда