ΠžΠ±Π·ΠΎΡ€

Enot Messenger ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ Redis-based rate limiting для Π·Π°Ρ‰ΠΈΡ‚Ρ‹ ΠΎΡ‚ Π·Π»ΠΎΡƒΠΏΠΎΡ‚Ρ€Π΅Π±Π»Π΅Π½ΠΈΠΉ ΠΈ DDoS Π°Ρ‚Π°ΠΊ. Π›ΠΈΠΌΠΈΡ‚Ρ‹ ΠΏΡ€ΠΈΠΌΠ΅Π½ΡΡŽΡ‚ΡΡ Π½Π° ΡƒΡ€ΠΎΠ²Π½Π΅ IP адрСса ΠΈ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ.

Π›ΠΈΠΌΠΈΡ‚Ρ‹ запросов

ΠžΠΏΠ΅Ρ€Π°Ρ†ΠΈΡ Π›ΠΈΠΌΠΈΡ‚ ΠŸΠ΅Ρ€ΠΈΠΎΠ΄ Scope
Login 5 ΠΏΠΎΠΏΡ‹Ρ‚ΠΎΠΊ 15 ΠΌΠΈΠ½ΡƒΡ‚ IP адрСс
Registration 3 Π°ΠΊΠΊΠ°ΡƒΠ½Ρ‚Π° 1 час IP адрСс
API Requests 100 запросов 1 ΠΌΠΈΠ½ΡƒΡ‚Π° User ID
Messages 30 сообщСний 1 ΠΌΠΈΠ½ΡƒΡ‚Π° User ID
File Upload 10 Ρ„Π°ΠΉΠ»ΠΎΠ² 1 ΠΌΠΈΠ½ΡƒΡ‚Π° User ID
WebSocket Messages 60-120 сообщСний 1 ΠΌΠΈΠ½ΡƒΡ‚Π° User ID

Response Headers

ΠŸΡ€ΠΈ достиТСнии Π»ΠΈΠΌΠΈΡ‚Π° API Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠ΅ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ:

X-RateLimit-Limit: 100 X-RateLimit-Remaining: 95 X-RateLimit-Reset: 1714932000 Retry-After: 60

ОписаниС заголовков

  • X-RateLimit-Limit - максимальноС количСство запросов
  • X-RateLimit-Remaining - ΠΎΡΡ‚Π°Π²ΡˆΠ΅Π΅ΡΡ количСство запросов
  • X-RateLimit-Reset - Unix timestamp ΠΊΠΎΠ³Π΄Π° Π»ΠΈΠΌΠΈΡ‚ сбросится
  • Retry-After - количСство сСкунд Π΄ΠΎ сброса Π»ΠΈΠΌΠΈΡ‚Π°

ΠžΡ‚Π²Π΅Ρ‚ ΠΏΡ€ΠΈ ΠΏΡ€Π΅Π²Ρ‹ΡˆΠ΅Π½ΠΈΠΈ Π»ΠΈΠΌΠΈΡ‚Π°

HTTP Status: 429 Too Many Requests

{ "code": "rate_limit_exceeded", "message": "Rate limit exceeded. Please try again later.", "details": { "retry_after": 60, "limit": 100, "window": "1 minute" } }

Π’Π°ΠΆΠ½ΠΎ: ΠŸΡ€ΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠΈ 429 ошибки, ΠΏΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ ΡƒΠΊΠ°Π·Π°Π½Π½ΠΎΠ΅ Π² retry_after количСство сСкунд ΠΏΠ΅Ρ€Π΅Π΄ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠΌ запросом.

Best Practices

1. Exponential Backoff

ΠŸΡ€ΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠΈ 429 ошибки ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ exponential backoff:

async function fetchWithRetry(url, options, maxRetries = 3) { let retries = 0; let delay = 1000; // 1 second while (retries < maxRetries) { try { const response = await fetch(url, options); if (response.status === 429) { const retryAfter = response.headers.get('Retry-After'); const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : delay; console.log(`Rate limited. Waiting ${waitTime}ms...`); await new Promise(resolve => setTimeout(resolve, waitTime)); retries++; delay *= 2; // Exponential backoff continue; } return response; } catch (error) { throw error; } } throw new Error('Max retries exceeded'); }

2. Request Batching

Π“Ρ€ΡƒΠΏΠΏΠΈΡ€ΡƒΠΉΡ‚Π΅ запросы вмСсто ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΈ мноТСства ΠΌΠ΅Π»ΠΊΠΈΡ…:

  • Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ ΠΏΠ°Π³ΠΈΠ½Π°Ρ†ΠΈΡŽ для получСния Π±ΠΎΠ»ΡŒΡˆΠΈΡ… списков
  • ΠšΡΡˆΠΈΡ€ΡƒΠΉΡ‚Π΅ Π΄Π°Π½Π½Ρ‹Π΅ локально
  • Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ WebSocket для real-time ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠΉ вмСсто polling

3. ΠœΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³ Π»ΠΈΠΌΠΈΡ‚ΠΎΠ²

ΠžΡ‚ΡΠ»Π΅ΠΆΠΈΠ²Π°ΠΉΡ‚Π΅ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ rate limit ΠΈ ΠΏΡ€Π΅Π΄ΡƒΠΏΡ€Π΅ΠΆΠ΄Π°ΠΉΡ‚Π΅ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ:

const remaining = parseInt(response.headers.get('X-RateLimit-Remaining')); const limit = parseInt(response.headers.get('X-RateLimit-Limit')); if (remaining < limit * 0.1) { console.warn('Approaching rate limit:', remaining, '/', limit); }

Как ΠΈΠ·Π±Π΅ΠΆΠ°Ρ‚ΡŒ rate limits

  • Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ WebSocket для real-time ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠΉ вмСсто polling
  • ΠšΡΡˆΠΈΡ€ΡƒΠΉΡ‚Π΅ Π΄Π°Π½Π½Ρ‹Π΅ локально (localStorage, IndexedDB)
  • Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ ΠΏΠ°Π³ΠΈΠ½Π°Ρ†ΠΈΡŽ для Π±ΠΎΠ»ΡŒΡˆΠΈΡ… списков
  • Π“Ρ€ΡƒΠΏΠΏΠΈΡ€ΡƒΠΉΡ‚Π΅ ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ (batch requests)
  • Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ debounce/throttle для частых ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΉ
  • ΠžΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°ΠΉΡ‚Π΅ 429 ошибки gracefully с retry logic
  • НС Π΄Π΅Π»Π°ΠΉΡ‚Π΅ запросы Π² Ρ†ΠΈΠΊΠ»Π°Ρ… Π±Π΅Π· Π·Π°Π΄Π΅Ρ€ΠΆΠ΅ΠΊ