Rate Limiting: Amikor az API-dnak szüksége van egy kis „lassító sávra”
Szóval épp egy publikus webes API szolgáltatáson dolgozol, ami egyre népszerűbb lesz. Az első öt percben minden csodálatosan működik, majd hirtelen jön a telefonhívás: „Az API lelassult, valaki percenként 10 ezer kérést küld!” Ismerős? Pontosan erre a problémára találták ki a rate limitinget, ami lényegében egy intelligens forgalomszabályozó a backend kapujában.
Miért nem csak egy sima „if” elágazás?
Rate limiting első hallásra egyszerűnek tűnik: „Csak számold a kéréseket, és ha túl sok, küldj egy hibát!” A valóságban azonban egy kritikus biztonsági és stabilitási mechanizmus. Nem csak arról van szó, hogy megvédjük a szerverünket a túlterheléstől (ami önmagában is elég fontos), hanem:
– Megakadályozzuk, hogy egyetlen felhasználó (akár véletlen, akár rosszindulatú) ellepje a sávszélességed – Garantáljuk az igazságos erőforrás-elosztást minden felhasználó között – Védjük a mögöttes adatbázisunkat és szolgáltatásainkat a DDoS-szerű támadásoktól – Akár különböző csomagokat (ingyenes, prémium) is tudjunk kezelni ennek segítségével
Egy alapvető PHP backend implementáció
A PHP világában a rate limiting implementációja gyakran a munkamenet-kezelésre vagy gyorsítótárra épül. Íme egy egyszerű, de hatékony megközelítés token bucket algoritmussal:
class RateLimiter {
private $cache;
private $limit;
private $window;
public function __construct($limit = 100, $window = 3600) {
// Használhatunk Redis-t, Memcached-et vagy akár fájl alapú cache-t
$this->cache = new Redis();
$this->cache->connect('127.0.0.1', 6379);
$this->limit = $limit;
$this->window = $window; // másodpercben
}
public function check($apiKey) {
$key = "rate_limit:$apiKey";
$current = $this->cache->get($key);
if ($current === false) {
// Első kérés ebben az ablakban
$this->cache->setex($key, $this->window, 1);
return true;
}
if ($current >= $this->limit) {
// Limitet túllépték
return false;
}
// Növeljük a számlálót
$this->cache->incr($key);
return true;
}
public function getRemaining($apiKey) {
$key = "rate_limit:$apiKey";
$current = $this->cache->get($key) ?: 0;
return max(0, $this->limit - $current);
}
}
// Használat:
$limiter = new RateLimiter(100, 3600); // 100 kérés/óra
$apiKey = $_SERVER['HTTP_API_KEY'] ?? '';
if (!$limiter->check($apiKey)) {
http_response_code(429); // Too Many Requests
echo json_encode(['error' => 'Rate limit exceeded. Try again later.']);
exit;
}
// API logika folytatódik...Frontend: Hogyan kommunikáljunk a felhasználóval?
Amikor a backend 429-es hibát küld, a frontendnek intelligensen kell kezelnie ezt. Itt egy jQuery alapú megoldás, ami Bootstrap komponensekkel illusztrálja a státuszt:
$(document).ready(function() {
let rateLimitRemaining = 100;
let rateLimitReset = null;
function makeApiCall() {
$.ajax({
url: '/api/endpoint',
method: 'GET',
headers: {
'API-Key': 'user_api_key_here'
},
success: function(data, textStatus, xhr) {
// Frissítjük a rate limit infókat a header-ekből
rateLimitRemaining = xhr.getResponseHeader('X-RateLimit-Remaining') || rateLimitRemaining;
rateLimitReset = xhr.getResponseHeader('X-RateLimit-Reset');
updateRateLimitDisplay();
},
error: function(xhr) {
if (xhr.status === 429) {
// Rate limit túllépve
const resetTime = xhr.getResponseHeader('X-RateLimit-Reset');
showRateLimitAlert(resetTime);
// Automatikus újrapróbálkozás a reset idő után
const waitTime = resetTime ?
(new Date(resetTime * 1000) - new Date()) :
60000;
setTimeout(makeApiCall, waitTime);
}
}
});
}
function updateRateLimitDisplay() {
const $indicator = $('#rateLimitIndicator');
const percentage = (rateLimitRemaining / 100) * 100;
// Bootstrap progress bar frissítése
$indicator.css('width', percentage + '%');
$indicator.text(rateLimitRemaining + ' kérés maradt');
// Szín változtatás a kihasználtság alapján
$indicator.removeClass('bg-success bg-warning bg-danger');
if (percentage > 50) {
$indicator.addClass('bg-success');
} else if (percentage > 20) {
$indicator.addClass('bg-warning');
} else {
$indicator.addClass('bg-danger');
}
}
function showRateLimitAlert(resetTime) {
// Bootstrap alert komponens
const $alert = $(`
<div class="alert alert-warning alert-dismissible fade show" role="alert">
Túl sok kérést küldött! Várjon amíg újra használhatja az API-t.
${resetTime ? `Újrapróbálás: ${new Date(resetTime * 1000).toLocaleTimeString()}` : ''}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
`);
$('#alertsContainer').append($alert);
// Alert automatikus eltüntetése
setTimeout(() => $alert.alert('close'), 10000);
}
// Kezdeti állapot
updateRateLimitDisplay();
});És egy kis SCSS a progress bar testreszabásához:
#rateLimitIndicator {
transition: width 0.3s ease, background-color 0.3s ease;
font-size: 0.8rem;
font-weight: bold;
&.bg-success {
box-shadow: 0 0 5px rgba($success, 0.5);
}
&.bg-warning {
box-shadow: 0 0 5px rgba($warning, 0.5);
}
&.bg-danger {
animation: pulse 1s infinite;
}
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.7; }
100% { opacity: 1; }
}Gyakori buktatók, amiket jobb előre tudni
1. Túl egyszerű számláló: Ha csak egy sima számlálót növelgetsz, akkor egy felhasználó az ablak elején elküldheti az összes kérést, majd várhat a következő ablakra. A sliding window vagy token bucket algoritmusok sokkal igazságosabbak.
2. Felejtetted el a reset headereket: A felhasználónak tudnia kell, mikor próbálkozzon újra. Az X-RateLimit-Reset header (UNIX timestamp formátumban) elengedhetetlen.
3. Minden endpointra ugyanaz a limit: Egy GET /users endpointnak és egy POST /transactions endpointnak lehet, hogy más a terhelése. Implementálj eltérő limiteket különböző útvonalakra.
4. LocalStorage/SessionStorage használata backend számláláshoz: Ez egy gyakori hibalehetőség. Ne feledd: a frontend kódot a felhasználó manipulálhatja. Minden számlálást a backendben kell végezni.
5. IP cím alapú limit túlzott használata: A NAT vagy proxy mögül több felhasználó is ugyanazzal az IP címmel érkezhet. Inkább API kulcsok alapján limitálj, ha lehetséges.
Összegzés
A rate limiting nem egy „nice-to-have” feature, hanem egy alapvető biztonsági és stabilitási követelmény minden publikus API szolgáltatásnál. Minél korábban implementálod a fejlesztési folyamatban, annál kevesebb fejfájást okoz később.
A kulcs a mértékletesség: a limitnek elég lazának kell lennie, hogy a normál felhasználók ne érezzék, de elég szigorúnak, hogy megvédjen a visszaélésektől. És ne feledd: mindig kommunikáld egyértelműen a felhasználóval, mi történik, amikor eléri a limitet – semmi sem frusztrálóbb, mint egy titokzatos hiba, ami „csak néha” jön elő.
A végére egy bölcsesség: a legjobb rate limiting az, amiről a felhasználóid nem is tudnak – amíg nem próbálnák meg túlterhelni a rendszeredet. Akkor viszont nagyon is érezniük kell a hatását.