cmatosbc / charon
Throttling in a simple and effective manner.
Requires
- nyholm/psr7: ^1.8
- psr/cache: ^3.0
- psr/http-message: ^2.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
- psr/log: ^3.0
- psr/simple-cache: ^3.0
Requires (Dev)
- phpunit/phpunit: ^11.4
This package is auto-updated.
Last update: 2025-02-28 22:10:50 UTC
README
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.
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.