Rate Limiter

Webman Rate Limiter supports annotation-based rate limiting. It supports apcu, redis, and memory drivers.

Source Code Address

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

Installation

composer require webman/rate-limiter

Usage

<?php
namespace app\controller;

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

class UserController
{

    #[RateLimiter(limit: 10)]
    public function index(): string
    {
        // The default rate limit is based on IP, with the default time unit being 1 second
        return 'Up to 10 requests per second per IP';
    }

    #[RateLimiter(limit: 100, ttl: 60, key: RateLimiter::UID)]
    public function search(): string
    {
        // key: RateLimiter::UID, rate limiting is done based on user ID, requiring session('user.id') to be non-empty
        return 'Up to 100 searches per user every 60 seconds';
    }

    #[RateLimiter(limit: 1, ttl: 60, key: RateLimiter::SID, message: 'Each person can only send 1 email per minute')]
    public function sendMail(): string
    {
        // key: RateLimiter::SID, rate limiting is done based on session_id
        return 'Email sent successfully';
    }

    #[RateLimiter(limit: 100, ttl: 24*60*60, key: 'coupon', message: 'Today’s coupons have all been distributed, please come back tomorrow')]
    #[RateLimiter(limit: 1, ttl: 24*60*60, key: RateLimiter::UID, message: 'Each user can only receive one coupon per day')]
    public function coupon(): string
    {
        // key: 'coupon', where coupon is a custom key, meaning that rate limiting is globally conducted based on the coupon key, allowing up to 100 coupons to be sent per day
        // At the same time, rate limiting is based on user ID, allowing each user to receive one coupon per day
        return 'Coupon sent successfully';
    }

    #[RateLimiter(limit: 5, ttl: 24*60*60, key: [UserController::class, 'getMobile'], message: 'Each mobile number can receive up to 5 messages per day')]
    public function sendSms2(): string
    {
        // When the key is a variable, you can get it using the [class, static method] format, for example, [UserController::class, 'getMobile'] will call the getMobile() method of UserController to get its return value as the key
        return 'SMS sent successfully';
    }

    /**
     * Custom key to get mobile number; must be a static method
     * @return string
     */
    public static function getMobile(): string
    {
        return request()->get('mobile');
    }

    #[RateLimiter(limit: 1, ttl: 10, key: RateLimiter::IP, message: 'Rate limit exceeded', exception: RuntimeException::class)]
    public function testException(): string
    {
        // The default exception when exceeded is Webman\RateLimiter\RateLimitException, which can be changed via the exception parameter
        return 'ok';
    }

}

Notes

  • The default time interval is 1 second
  • You can set the time interval using ttl, for example, ttl:60 means 60 seconds
  • The default rate limiting dimension is based on IP (default 127.0.0.1 has no rate limit, see the configuration section below)
  • It supports built-in IP rate limiting, UID rate limiting (requiring session('user.id') to be non-empty), and SID rate limiting (based on session_id)
  • If you are using nginx as a proxy, for IP rate limiting you need to forward the X-Forwarded-For header, see nginx proxy
  • When the limit is exceeded, a Webman\RateLimiter\RateLimitException exception will be triggered, and you can customize the exception class using exception:xx
  • When an exception is triggered due to exceeding the limit, the default error message is Too Many Requests, which can be customized using message:xx
  • The default error message can also be modified through Localization, for Linux, refer to the following commands
    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

Interface

Sometimes developers want to directly call the rate limiter from the code, as shown in the code below

<?php
namespace app\controller;

use RuntimeException;
use Webman\RateLimiter\Limiter;

class UserController {

    public function sendSms(string $mobile): string
    {
        // Here mobile is used as the key
        Limiter::check($mobile, 5, 24*60*60, 'Each mobile number can receive up to 5 messages per day');
        return 'SMS sent successfully';
    }
}

Configuration

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

<?php
return [
    'enable' => true,
    'driver' => 'auto', // auto, apcu, memory, redis
    'stores' => [
        'redis' => [
            'connection' => 'default',
        ]
    ],
    // Requests from these IPs will not be rate limited (only effective when the key is RateLimiter::IP)
    'ip_whitelist' => [
        '127.0.0.1',
    ],
];
  • enable: Whether to enable rate limiting
  • driver: One of auto, apcu, memory, redis, when using auto, it will automatically select one from apcu and memory
  • stores: redis configuration, where connection corresponds to the key in config/redis.php
  • ip_whitelist: Whitelisted IPs will not be rate limited (only effective when the key is RateLimiter::IP)

Driver Selection

memory

  • Introduction
    No extensions are required, and it offers the best performance.

  • Usage Limitations
    Rate limiting is only effective within the current process; rate limiting data is not shared across multiple processes, nor does it support cluster rate limiting.

  • Applicable Scenarios
    Windows development environments; businesses that do not require strict rate limiting; resisting CC attacks.

apcu

  • Installation of Extension
    You need to install the apcu extension and set in php.ini

    apc.enabled=1
    apc.enable_cli=1

    If you do not know the location of php.ini, you can find it using the command php --ini

  • Introduction
    Slightly lower performance than memory, supports multi-process sharing of rate limiting data.

  • Usage Limitations
    Does not support clustering.

  • Applicable Scenarios
    Any development environment; online single machine rate limiting scenarios; scenarios where strict rate limiting is not required in a cluster; resisting CC attacks.

redis

  • Dependencies
    You need to install the redis extension and install the Redis component, with the installation command

    composer require -W illuminate/redis illuminate/events
  • Introduction
    Performance is lower than apcu and supports both single machine and cluster precise rate limiting.

  • Applicable Scenarios
    Development environments; online single machine environments; cluster environments.