Implementando un Middleware de Rate Limiting Adaptativo en PHP
El rate limiting es una técnica esencial para proteger las APIs y servicios web de abusos, ataques DDoS y sobrecarga. Tradicionalmente, se implementan límites fijos, pero un enfoque adaptativo puede mejorar la disponibilidad y la experiencia del usuario al ajustar los límites dinámicamente según las condiciones del sistema.
En este post, exploraremos cómo implementar un middleware de rate limiting adaptativo en PHP utilizando un esquema sencillo basado en el tiempo de respuesta promedio. El middleware ajustará los límites de solicitudes por minuto para cada cliente en función del tiempo que tarda el servidor en procesar las solicitudes.
<?php
namespace App\Middleware;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as Handler;
use Slim\Psr7\Response;
use Predis\Client; // Requerimos predis/predis
class AdaptiveRateLimiter
{
private $redis;
private $maxRequests; // Limite máximo por minuto
private $responseTimeThreshold; // Tiempo de respuesta en ms que dispara la reducción del limite.
private $reductionFactor; //Factor de reducción del limite.
public function __construct(Client $redis, int $maxRequests = 60, int $responseTimeThreshold = 200, float $reductionFactor = 0.8)
{
$this->redis = $redis;
$this->maxRequests = $maxRequests;
$this->responseTimeThreshold = $responseTimeThreshold;
$this->reductionFactor = $reductionFactor;
}
public function __invoke(Request $request, Handler $handler): Response
{
$ipAddress = $_SERVER['REMOTE_ADDR']; // Identificar al cliente por IP (podría ser un header, API Key, etc.)
$key = 'rate_limit:' . $ipAddress . ':' . date('YmdH'); // Clave para Redis (una por IP por hora)
$requestCount = (int) $this->redis->get($key) ?? 0;
$startTime = microtime(true);
$response = $handler->handle($request);
$endTime = microtime(true);
$responseTime = ($endTime - $startTime) * 1000; // Tiempo de respuesta en milisegundos
if ($requestCount >= $this->maxRequests) {
$response = new Response();
$response->getBody()->write('Too Many Requests');
return $response->withStatus(429)->withHeader('Retry-After', '60'); // Retry after 60 seconds
}
$this->redis->incr($key);
$this->redis->expire($key, 3600); // Expira en 1 hora.
if ($responseTime > $this->responseTimeThreshold) {
// Ajustar el limite de solicitudes si el tiempo de respuesta es alto.
$this->maxRequests = (int)($this->maxRequests * $this->reductionFactor);
error_log("Rate limit reducido para $ipAddress a " . $this->maxRequests . " requests/min debido a tiempo de respuesta alto.");
}
return $response;
}
}
Este middleware, `AdaptiveRateLimiter`, utiliza Redis para almacenar el conteo de solicitudes por cliente. Se inicializa con un límite máximo de solicitudes (`maxRequests`), un umbral de tiempo de respuesta (`responseTimeThreshold`) y un factor de reducción (`reductionFactor`). Si el tiempo de respuesta de una solicitud excede el umbral, el límite máximo se reduce multiplicando el límite actual por el factor de reducción. Esto permite que el middleware se adapte a la carga del servidor y prevenir una degradación del servicio.
Para usarlo, necesitarás instalar predis/predis (`composer require predis/predis`) y configurar una instancia de `Predis\Client` para acceder a tu servidor Redis. Luego, puedes agregar este middleware a tu aplicación Slim (o similar).
<?php
use Slim\Factory\AppFactory;
use App\Middleware\AdaptiveRateLimiter;
use Predis\Client;
require __DIR__ . '/../vendor/autoload.php';
$app = AppFactory::create();
$redis = new Client([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
]);
$rateLimiter = new AdaptiveRateLimiter($redis, 100, 200, 0.75); // Ajusta los parámetros según sea necesario
$app->add($rateLimiter);
$app->get('/api/data', function ($request, $response, $args) {
$response->getBody()->write("Hello, World!");
return $response;
});
$app->run();
Este ejemplo muestra cómo integrar el middleware en una aplicación Slim. Recuerda ajustar los parámetros (`maxRequests`, `responseTimeThreshold`, `reductionFactor`) según las necesidades de tu aplicación y las características de tu infraestructura. También, considera implementar un mecanismo para aumentar el límite con el tiempo si el servidor tiene baja carga.
No hay comentarios:
Publicar un comentario