Update: 2026-05-06 05:11:51

This commit is contained in:
Hamza-Ayed
2026-05-06 05:11:51 +03:00
parent 01234bf3f2
commit a9a2c65bee
8 changed files with 359 additions and 53 deletions

View File

@@ -15,54 +15,61 @@ final class RateLimitMiddleware
*/
public static function check(int $maxRequests = 60, int $timeWindow = 60): void
{
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
$key = 'rl:' . md5($ip);
// 1. Try Redis first
$redis = \App\Core\Cache::getInstance();
if ($redis) {
try {
$count = $redis->get($key);
if ($count && (int)$count >= $maxRequests) {
header('Retry-After: ' . $timeWindow);
json_error('Too Many Requests. Please slow down.', 429);
}
if (!$count) {
$redis->setex($key, $timeWindow, 1);
} else {
$redis->incr($key);
}
return; // Success with Redis
} catch (\Exception $e) {
// Fallback to file-based if Redis fails
}
}
// 2. Fallback: File-based rate limiter (original logic)
$cacheDir = STORAGE_PATH . '/cache';
$cacheFile = $cacheDir . '/rl_' . md5($ip) . '.json';
if (!is_dir($cacheDir)) mkdir($cacheDir, 0755, true);
if (!is_dir($cacheDir)) {
mkdir($cacheDir, 0755, true);
}
// M2 Fix: Use exclusive file lock to prevent race condition
$fp = fopen($cacheFile, 'c+');
if ($fp === false) {
// If we can't open the file, fail open (don't block all users)
return;
}
if ($fp === false) return;
try {
flock($fp, LOCK_EX); // Exclusive lock — blocks until acquired
$now = time();
$content = stream_get_contents($fp);
flock($fp, LOCK_EX);
$now = time();
$content = stream_get_contents($fp);
$requests = [];
if (!empty($content)) {
$decoded = json_decode($content, true);
if (is_array($decoded)) {
// Keep only requests within the time window
$requests = array_values(
array_filter($decoded, fn($ts) => $ts > ($now - $timeWindow))
);
$requests = array_values(array_filter($decoded, fn($ts) => $ts > ($now - $timeWindow)));
}
}
if (count($requests) >= $maxRequests) {
flock($fp, LOCK_UN);
fclose($fp);
header('Retry-After: ' . $timeWindow);
json_error('Too Many Requests. Please slow down.', 429);
}
// Record this request
$requests[] = $now;
// Write updated data back
ftruncate($fp, 0);
rewind($fp);
fwrite($fp, json_encode($requests));
} finally {
flock($fp, LOCK_UN);
fclose($fp);