50 lines
1.6 KiB
PHP
50 lines
1.6 KiB
PHP
<?php
|
|
namespace App\Middleware;
|
|
|
|
use App\Helpers\Response;
|
|
use App\Helpers\Security;
|
|
use App\Models\Model;
|
|
|
|
/**
|
|
* Class RateLimitMiddleware
|
|
* Enterprise API Rate Limiter & DDOS Mitigation Guard
|
|
*
|
|
* @package App\Middleware
|
|
*/
|
|
class RateLimitMiddleware
|
|
{
|
|
/**
|
|
* Handle rate limiting check (Max 60 requests per minute per IP)
|
|
*/
|
|
public static function handle(int $maxRequests = 60, int $windowSeconds = 60): void
|
|
{
|
|
$ip = Security::getClientIp();
|
|
$key = "rate_limit:ip:" . md5($ip);
|
|
|
|
if (session_status() === PHP_SESSION_NONE) {
|
|
session_start();
|
|
}
|
|
|
|
// Use Session / APCu / Redis / File-based tracking for clean fallback without crashing if Redis is offline
|
|
$now = time();
|
|
$record = $_SESSION[$key] ?? ['count' => 0, 'start' => $now];
|
|
|
|
if (($now - $record['start']) >= $windowSeconds) {
|
|
$record = ['count' => 1, 'start' => $now];
|
|
} else {
|
|
$record['count']++;
|
|
}
|
|
|
|
$_SESSION[$key] = $record;
|
|
|
|
// Set Rate Limit Headers
|
|
header("X-RateLimit-Limit: {$maxRequests}");
|
|
header("X-RateLimit-Remaining: " . max(0, $maxRequests - $record['count']));
|
|
header("X-RateLimit-Reset: " . ($record['start'] + $windowSeconds));
|
|
|
|
if ($record['count'] > $maxRequests) {
|
|
Response::error("Too Many Requests: ท่านส่งคำขอเข้ามาถี่เกินไป กรุณารอ 1 นาทีก่อนลองใหม่อีกครั้ง (DDOS Protection)", 429);
|
|
}
|
|
}
|
|
}
|