Модуль 15

Rate limiting та захист від DDoS

Ось готовий урок, створений спеціально для тебе у стилі курсу CS50. Приготуйся, сьогодні ми розберемо тему, яка рятує інтернет від колапсу!


🎓 CS50: Rate Limiting та захист від DDoS

👋 Привіт, друзі! Радий бачити вас.

Сьогодні ми поговоримо про безпеку та стабільність. Про те, що стоїть між вашим сервером і мільйоном розлючених запитів, які прагнуть його знищити.

1. 🔥 Вступ: проблема та мотивація

Уявіть собі ситуацію. Ви відкрили найкращу піцерію в місті. Ви робите неймовірну піцу. Але у вас на кухні працює лише один кухар — назвемо його Сервер. Він може приготувати одну піцу за 2 хвилини.

І ось ви оголошуєте акцію: "Безкоштовна піца всім, хто зайде в двері о 12:00!"

Що станеться? У двері одночасно намагаються пролізти 5000 людей. 1. Ваш кухар (Сервер) в шоці. 2. Ті, хто прийшов чесно купити каву, не можуть зайти, бо в дверях тиснява. 3. Піцерія зупиняється. Ніхто нічого не отримує.

У світі вебу це називається Denial of Service (DoS) — відмова в обслуговуванні.

А тепер уявіть, що ці 5000 людей — це не голодні студенти, а спеціально найняті роботи, які прийшли тільки для того, щоб заблокувати ваші двері. Це вже DDoS (Distributed Denial of Service).

Питання до вас: Як нам врятувати піцерію, не зачиняючи її повністю? Чи маємо ми право сказати клієнту: "Гей, друже, ти занадто швидко їси, почекай 5 хвилин"?

Спойлер: Так, маємо. Саме про це сьогоднішня тема — Rate Limiting (обмеження швидкості). Без нього будь-який ваш стартап впаде від першого ж скрипта, написаного школярем-хакером.


2. 🧠 Теоретична база (без сухої академічності)

Давайте розберемося, що відбувається "під капотом", коли ми ставимо охоронця на вході.

Що таке Rate Limiting?

Це механізм, який каже: "Цей користувач (IP-адреса) може зробити не більше X запитів за час Y".

Уявіть фейс-контроль у клубі. Охоронець має лічильник. * Ти зайшов? Клік. Один. * Зайшов знову? Клік. Два. * Спробував зайти 100 разів за хвилину? Охоронець каже: "Стоп. Охолонь на 60 секунд".

Як це працює алгоритмічно?

Існує кілька підходів, але інтуїтивно найпростіший — це Token Bucket (Відро з жетонами).

Уявіть відро, в якому лежать жетони (токени). 1. Кожен запит користувача забирає 1 жетон. 2. Якщо у відрі є жетони — запит проходить. 3. Якщо відро порожнє — запит відхиляється (ви отримуєте помилку). 4. Магія в тому, що жетони додаються у відро автоматично з певною швидкістю (наприклад, 1 жетон кожну секунду).

❗️ Що треба запам’ятати залізно: * HTTP 429 Too Many Requests — це стандартний код відповіді, який ми повертаємо, коли ліміт перевищено. Це ввічливе "Відчепися на хвилинку". * Rate Limiting захищає не тільки від атак, а й від багів (коли ваш власний фронтенд випадково шле 1000 запитів замість одного).


3. 🧪 Приклади (від простого до реального)

Давайте напишемо код. Уявімо, що ми пишемо логіку на Python (але логіка однакова для JS, Go чи Java).

Приклад 1: Наївна реалізація (словник у пам'яті)

Що ми очікуємо? Ми хочемо зберігати IP-адресу і час останнього візиту. Якщо клієнт прийшов занадто швидко — блокуємо.

import time

# Наша "база даних" у пам'яті: { "IP-адреса": час_останнього_запиту }
last_request_time = {}

def handle_request(ip_address):
    current_time = time.time()

    # Перевіряємо, чи був цей клієнт раніше
    if ip_address in last_request_time:
        last_visit = last_request_time[ip_address]

        # Якщо пройшло менше 1 секунди — блокуємо!
        if current_time - last_visit < 1.0:
            return 429, "Занадто швидко! Почекай."

    # Оновлюємо час і пускаємо
    last_request_time[ip_address] = current_time
    return 200, "Ласкаво просимо!"

Аналіз: Це працює для одного спамера. Але що, якщо він робить 1 запит раз на 1.1 секунди? Він пройде. А якщо таких користувачів мільйон? Наш словник last_request_time з’їсть всю оперативну пам'ять сервера і сервер впаде.

Приклад 2: Лічильник за вікно часу (Sliding Window)

Тепер трохи серйозніше. Ми хочемо дозволити 5 запитів на хвилину. Не важливо, як швидко вони йдуть, головне — не більше 5 у сумі.

# { "IP": [список_часу_запитів] }
requests_history = {}

def rate_limiter(ip):
    now = time.time()

    # Створюємо історію для нового IP
    if ip not in requests_history:
        requests_history[ip] = []

    # 1. Прибираємо старі запити (старші за 60 секунд)
    # Ми "чистимо" історію, залишаючи тільки актуальні
    requests_history[ip] = [t for t in requests_history[ip] if now - t < 60]

    # 2. Перевіряємо кількість
    if len(requests_history[ip]) >= 5:
        return 429, "Ліміт вичерпано (5 запитів/хв)"

    # 3. Додаємо поточний запит
    requests_history[ip].append(now)
    return 200, "OK"

Чому це краще? Це дає гнучкість. Користувач може зробити 5 швидких кліків (burst), а потім чекати. Це природна поведінка людини.


4. 🛠 Практична частина

Час закачати рукави! У вас є логіка з Прикладу 2.

Завдання 1: Зміна умов 🔹 Змініть код так, щоб ліміт був 10 запитів за 30 секунд. Які числа в коді треба змінити?

Завдання 2: "VIP-клієнти" 🔹 Уявіть, що у вас є список IP-адрес адміністраторів (admin_ips = ['1.2.3.4']). Допишіть умову: якщо IP в цьому списку, ліміти на нього не діють.

Завдання 3: Реальна проблема (Memory Leak) 🔹 Подивіться на requests_history у Прикладі 2. Що станеться, якщо до нас зайде 100,000 різних IP-адрес один раз і більше ніколи не повернуться? Підказка: Чи видаляємо ми коли-небудь ключі зі словника? Як це виправити?

Завдання 4: Міні-кейс 🔹 Ви захищаєте форму входу (Login). Чи має сенс ставити ліміт "1000 запитів на хвилину"? Чи краще "5 спроб на хвилину"? Чому для логіну правила мають бути суворіші, ніж для перегляду головної сторінки?

Завдання 5: А що, якщо... 🔹 А що, якщо зловмисник має 1000 різних IP-адрес (ботнет)? Кожен його IP робить лише 1 запит, тому Rate Limiter його не блокує. Але сервер падає. Подумайте: Як можна обмежити загальну кількість запитів до сервера, незалежно від IP?


5. 💡 Мислення як у розробника

Як відрізнити новачка від профі в цій темі?

  1. Новачок зберігає ліміти в пам'яті самого веб-сервера (як у наших прикладах вище).
    • Проблема: Коли ви запустите 5 копій вашого сервера (щоб витримати навантаження), кожен сервер матиме свій лічильник. Користувач зможе зробити в 5 разів більше запитів.
  2. Профі використовує зовнішнє сховище, наприклад Redis.
    • Це як "спільний блокнот" для всіх охоронців. Якщо один охоронець записав порушення, всі інші це теж бачать.

Типові помилки: * ❌ Блокувати статичні файли. Не ставте жорсткий ліміт на завантаження logo.png або style.css. Браузер може запитувати їх дуже часто, і це нормально. * ❌ Не повідомляти, коли можна повернутися. Хороший тон — додати заголовок Retry-After: 30 (спробуй через 30 секунд) у відповідь 429.


6. 🧩 Підсумок

Отже, що ми сьогодні зрозуміли?

  1. Ресурси сервера не безкінечні. Ми повинні їх захищати.
  2. Rate Limiting — це регулювальник, який каже "не так швидко".
  3. DDoS — це натовп, який хоче вас розчавити, і Rate Limiting — це перша лінія оборони.
  4. Технічно це реалізується через лічильники та час.

Що ви тепер вмієте? Ви можете пояснити, чому сайт іноді видає помилку 429, і навіть написати простий алгоритм захисту свого API. Ви вже не просто пишете код, ви проектуєте стійкі системи.

🔜 Тизер наступного уроку: Ми навчилися відбиватися від зайвих запитів. Але як обробити ті, що пройшли, максимально швидко? Наступного разу ми поговоримо про Кешування (Caching) — мистецтво пам'ятати відповіді, щоб не працювати двічі.

А поки що — це був CS50! 💻