PHP Classes

How Can PHP Limit Requests Per Second Using the Throttling Middleware PackageCharon - Modern Rate Limiter and Throttler for PHP: Limit the number of accesses of users to sites

Recommend this page to a friend!
  Info   Documentation   View files Files   Install with Composer Install with Composer   Download Download   Reputation   Support forum   Blog    
Last Updated Ratings Unique User Downloads Download Rankings
2024-12-13 (16 hours ago) RSS 2.0 feedNot enough user ratingsTotal: 15 This week: 4All time: 11,411 This week: 15Up
Version License PHP version Categories
charon 1.0.1GNU General Publi...8.1HTTP, Logging, Design Patterns, Perfo..., P..., P...
Description 

Author

This package can limit the number of users who access sites.

It provides a middleware class that implements the PSR-15 compliant middleware interface to limit the number of accesses of users or robots that access a site too many times.

The middleware class can respond with an HTTP 403 status and headers with the times the current user or robot has accessed and the remaining times it may access before it reaches the allowed limit.

The middleware class supports logging accesses that were subject to limit restrictions.

The package provides examples to use it with frameworks like Slim, Laravel Symfony, and the WordPress API.

Picture of Carlos Artur Curvelo da Matos
  Performance   Level  
Name: Carlos Artur Curvelo da ... <contact>
Classes: 25 packages by
Country: Portugal Portugal
Innovation award
Innovation award
Nominee: 16x

Winner: 2x

Documentation

Charon

A simple yet powerful PSR-15 compliant rate limiting middleware for PHP applications. Charon provides an effective way to protect your applications from abuse through configurable request throttling.

Software License

Features

  • ? PSR-15 Middleware compliant
  • ? PSR-16 Simple Cache support for storage
  • ? Optional PSR-3 Logger integration
  • ? Efficient rate limiting using sliding window
  • ? IP and User-Agent based throttling
  • ? Configurable rate limits and time windows
  • ? Standard rate limit headers (X-RateLimit-*)
  • ? Automatic blacklisting for repeat offenders

Installation

You can install the package via composer:

composer require cmatosbc/charon

Usage

Basic Usage

use Charon\ThrottleMiddleware;

// Create the middleware with basic configuration
$middleware = new ThrottleMiddleware(
    limit: 100,           // Maximum requests allowed
    windowPeriod: 3600,   // Time window in seconds (1 hour)
    cache: $cacheImpl     // PSR-16 cache implementation
);

// Add it to your middleware stack
$app->add($middleware);

With Logging

use Charon\ThrottleMiddleware;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

// Create a PSR-3 logger
$logger = new Logger('rate-limits');
$logger->pushHandler(new StreamHandler('path/to/rate-limit.log', Logger::WARNING));

// Create middleware with logging
$middleware = new ThrottleMiddleware(
    limit: 100,
    windowPeriod: 3600,
    cache: $cacheImpl,
    logger: $logger,          // PSR-3 logger
    logAllRequests: false     // Set to true to log all requests
);

With Automatic Blacklisting

use Charon\ThrottleMiddleware;

$middleware = new ThrottleMiddleware(
    limit: 100,
    windowPeriod: 3600,
    cache: $cacheImpl,
    logger: $logger
);

// Blacklist clients after 5 rate limit violations
$middleware->maybeBlacklist(5);

When blacklisting is enabled: - Clients exceeding rate limits multiple times will be tracked - After reaching the specified number of violations, the client will be blacklisted - Blacklisted clients receive a 403 Forbidden response - Violations are tracked across multiple time windows - Blacklist status is stored in cache with client signature

Framework Integration Examples

Slim 4

use Slim\Factory\AppFactory;
use Charon\ThrottleMiddleware;

$app = AppFactory::create();

// Add the middleware with blacklisting
$app->add((new ThrottleMiddleware(
    limit: 100,
    windowPeriod: 3600,
    cache: $cache
))->maybeBlacklist(5));

Laravel

use Charon\ThrottleMiddleware;

// In a service provider
public function boot()
{
    $this->app->middleware([
        (new ThrottleMiddleware(
            limit: 100,
            windowPeriod: 3600,
            cache: app()->make('cache.store')
        ))->maybeBlacklist(5)
    ]);
}

Symfony

use Charon\ThrottleMiddleware;
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\Cache\Psr16Cache;

// In services.yaml
services:
    Charon\ThrottleMiddleware:
        arguments:
            $limit: 100
            $windowPeriod: 3600
            $cache: '@cache.app'
        calls:
            - maybeBlacklist: [5]
        tags:
            - { name: 'kernel.event_listener', event: 'kernel.request', priority: 300 }

// Or in a Controller/EventSubscriber
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;

class RateLimitSubscriber implements EventSubscriberInterface
{
    private ThrottleMiddleware $throttle;

    public function __construct(
        private Psr16Cache $cache
    ) {
        $this->throttle = (new ThrottleMiddleware(
            limit: 100,
            windowPeriod: 3600,
            cache: $this->cache
        ))->maybeBlacklist(5);
    }

    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::REQUEST => ['onKernelRequest', 300]
        ];
    }

    public function onKernelRequest(RequestEvent $event): void
    {
        if (!$event->isMainRequest()) {
            return;
        }

        $request = $event->getRequest();
        $handler = new class implements RequestHandlerInterface {
            public function handle(ServerRequestInterface $request): ResponseInterface
            {
                return new Response();
            }
        };

        $response = $this->throttle->process($request, $handler);
        if ($response->getStatusCode() !== 200) {
            $event->setResponse($response);
        }
    }
}

WordPress REST API

use Charon\ThrottleMiddleware;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Psr16Cache;
use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7\ServerRequest;

// In your plugin file or functions.php
add_action('rest_api_init', function () {
    $cache = new Psr16Cache(new FilesystemAdapter());
    $throttle = (new ThrottleMiddleware(
        limit: 100,
        windowPeriod: 3600,
        cache: $cache
    ))->maybeBlacklist(5);

    // Apply to all REST API endpoints
    add_filter('rest_pre_dispatch', function ($result, $server, $request) use ($throttle) {
        if (null !== $result) {
            return $result;
        }

        // Convert WordPress request to PSR-7
        $psr17Factory = new Psr17Factory();
        $psrRequest = new ServerRequest(
            $request->get_method(),
            $request->get_route(),
            getallheaders(),
            null,
            '1.1',
            array_merge($_SERVER, ['REMOTE_ADDR' => $_SERVER['REMOTE_ADDR']])
        );

        // Handle rate limiting
        $handler = new class implements RequestHandlerInterface {
            public function handle(ServerRequestInterface $request): ResponseInterface
            {
                return (new Psr17Factory())->createResponse(200);
            }
        };

        $response = $throttle->process($psrRequest, $handler);
        
        // Check if request should be blocked
        if ($response->getStatusCode() !== 200) {
            return new WP_Error(
                'rest_throttled',
                $response->getReasonPhrase(),
                ['status' => $response->getStatusCode()]
            );
        }

        // Add rate limit headers to WordPress response
        add_filter('rest_post_dispatch', function ($response) use ($throttle) {
            if ($response instanceof WP_REST_Response) {
                foreach ($response->get_headers() as $key => $value) {
                    if (strpos($key, 'X-RateLimit') === 0) {
                        $response->header($key, $value);
                    }
                }
            }
            return $response;
        });

        return $result;
    }, 10, 3);
});

Response Headers

The middleware adds standard rate limit headers to responses:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1635789600

When the rate limit is exceeded, a 429 (Too Many Requests) response is returned with:

Status: 429 Too Many Requests
Retry-After: 3600
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1635789600

When a blacklisted client attempts to access the resource:

Status: 403 Forbidden
Content-Type: application/json

{
    "error": "Access denied due to repeated rate limit violations"
}

Logging

When logging is enabled, the middleware logs the following information:

Rate Limit Exceeded (Warning Level)

{
    "message": "Rate limit exceeded",
    "context": {
        "client": {
            "ip": "192.168.1.1",
            "user_agent": "Mozilla/5.0...",
            "method": "GET",
            "path": "/api/resource"
        },
        "requests": 101,
        "limit": 100,
        "reset_time": 1635789600
    }
}

Client Blacklisted (Alert Level)

{
    "message": "Client blacklisted due to recurring rate limit violations",
    "context": {
        "client": {
            "ip": "192.168.1.1",
            "user_agent": "Mozilla/5.0...",
            "method": "GET",
            "path": "/api/resource"
        },
        "violations": 5,
        "threshold": 5
    }
}

Request Processed (Info Level, when logAllRequests is true)

{
    "message": "Request processed",
    "context": {
        "client": {
            "ip": "192.168.1.1",
            "user_agent": "Mozilla/5.0...",
            "method": "GET",
            "path": "/api/resource"
        },
        "requests": 50,
        "limit": 100,
        "remaining": 50
    }
}

Use Cases

  • API Rate Limiting: Protect your API from abuse by limiting requests per client
  • Login Throttling: Prevent brute force attacks by limiting login attempts
  • Resource Protection: Protect expensive operations from overuse
  • DDoS Mitigation: Basic protection against distributed denial of service attacks
  • Fair Usage: Ensure fair resource distribution among clients
  • Abuse Prevention: Automatically block repeat offenders with blacklisting

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

The GNU General Public License v3.0. Please see License File for more information.


  Files folder image Files (10)  
File Role Description
Files folder image.github (1 directory)
Files folder imagesrc (1 file)
Files folder imagetests (2 files, 1 directory)
Accessible without login Plain text file composer.json Data Auxiliary data
Accessible without login Plain text file LICENSE Lic. License text
Accessible without login Plain text file phpunit.xml Data Auxiliary data
Accessible without login Plain text file README.md Doc. Documentation

  Files folder image Files (10)  /  .github  
File Role Description
Files folder imageworkflows (1 file)

  Files folder image Files (10)  /  .github  /  workflows  
File Role Description
  Accessible without login Plain text file php.yml Data Auxiliary data

  Files folder image Files (10)  /  src  
File Role Description
  Plain text file ThrottleMiddleware.php Class Class source

  Files folder image Files (10)  /  tests  
File Role Description
Files folder imageMock (2 files)
  Plain text file TestCase.php Class Class source
  Plain text file ThrottleMiddlewareTest.php Class Class source

  Files folder image Files (10)  /  tests  /  Mock  
File Role Description
  Plain text file ArrayCache.php Class Class source
  Plain text file TestLogger.php Class Class source

The PHP Classes site has supported package installation using the Composer tool since 2013, as you may verify by reading this instructions page.
Install with Composer Install with Composer
 Version Control Unique User Downloads Download Rankings  
 100%
Total:15
This week:4
All time:11,411
This week:15Up