Уведомления об аномальном трафике и абьюзе API
Пока проект маленький, полноценной защиты от абьюза обычно нет. А между тем один клиент, разгоняющий ваш эндпойнт, — это и счёт за трафик, и упавший сервис, и потенциальный скрейпинг. Ставим лёгкую middleware: считаем запросы по IP или API-ключу и шлём пуш, когда кто-то резко разгоняется или пошла волна 429.
Общие правила
Заголовок раздела «Общие правила»- Только на сервере. Считаем на бэкенде,
notify()вызываем оттуда. - Тротлинг самих пушей. Во время атаки не превращайте Notifly в тот же шторм — один пуш на клиента раз в N минут.
- Отличайте всплеск от запуска. Резкий рост трафика может быть не абьюзом, а вашим же маркетинговым запуском. Смотрите на концентрацию: один IP/ключ даёт большую долю запросов — это абьюз; трафик равномерно размазан по многим адресам — это, скорее, наплыв реальных людей.
Базовая обёртка
Заголовок раздела «Базовая обёртка»Берём notify() из обзорной страницы:
import os, requests
def notify(title, message, priority=5): requests.post( f"{os.environ['NOTIFLY_URL']}/message", params={"token": os.environ["NOTIFLY_TOKEN"]}, json={"title": title, "message": message, "priority": priority}, timeout=5, )Node.js (Express)
Заголовок раздела «Node.js (Express)»Скользящее окно на IP; пуш, когда один клиент пробивает лимит:
const {notify} = require('./notifly');
const WINDOW_MS = 60_000; // окно 1 минутаconst LIMIT = 300; // порог запросов от одного клиента за окноconst NOTIFY_COOLDOWN = 300_000;
const hits = new Map(); // ip -> [timestamps]const lastNotify = new Map(); // ip -> ts
function rateGuard(req, res, next) { const ip = req.ip; const now = Date.now(); const arr = (hits.get(ip) || []).filter(t => now - t < WINDOW_MS); arr.push(now); hits.set(ip, arr);
if (arr.length > LIMIT) { // отличаем спайк от общего наплыва: доля этого IP во всём трафике const total = [...hits.values()].reduce((s, a) => s + a.length, 0); const share = arr.length / total; if (share > 0.5 && now - (lastNotify.get(ip) || 0) > NOTIFY_COOLDOWN) { lastNotify.set(ip, now); notify( '🚦 Аномальный трафик', `IP ${ip}: ${arr.length} req/min (${(share * 100).toFixed(0)}% всего трафика)\n` + `Путь: ${req.method} ${req.path}`, 8 ); } return res.status(429).json({error: 'rate limit'}); } next();}
app.use(rateGuard);Python (FastAPI)
Заголовок раздела «Python (FastAPI)»То же на FastAPI: middleware со скользящим окном и «долей клиента»:
import timefrom collections import defaultdict, dequefrom fastapi import FastAPI, Requestfrom fastapi.responses import JSONResponsefrom notifly import notify
app = FastAPI()
WINDOW = 60 # окно в секундахLIMIT = 300 # порог запросов от одного клиентаNOTIFY_COOLDOWN = 300
hits: dict[str, deque] = defaultdict(deque)last_notify: dict[str, float] = defaultdict(float)
@app.middleware("http")async def rate_guard(request: Request, call_next): ip = request.client.host now = time.time() q = hits[ip] q.append(now) while q and now - q[0] > WINDOW: q.popleft()
if len(q) > LIMIT: total = sum(len(v) for v in hits.values()) share = len(q) / total if total else 0 # спайк одного клиента, а не общий наплыв if share > 0.5 and now - last_notify[ip] > NOTIFY_COOLDOWN: last_notify[ip] = now notify( "🚦 Аномальный трафик", f"IP {ip}: {len(q)} req/min ({share*100:.0f}% всего трафика)\n" f"Путь: {request.method} {request.url.path}", 8, ) return JSONResponse({"error": "rate limit"}, status_code=429)
return await call_next(request)Спайк против запуска: тихий сигнал о наплыве
Заголовок раздела «Спайк против запуска: тихий сигнал о наплыве»Если трафик вырос, но размазан по многим адресам — это, скорее всего, хорошие новости (реклама сработала). Такое тоже полезно знать, но тихо:
# отдельная тихая проверка раз в минуту (например, из фоновой задачи)def traffic_pulse(rpm: int, unique_ips: int): if rpm > 1000 and unique_ips > 100: # много запросов от многих людей — это наплыв, не абьюз notify("📊 Наплыв трафика", f"{rpm} req/min от {unique_ips} IP — похоже, запуск зашёл", 4)Что ещё присылать
Заголовок раздела «Что ещё присылать»- Много 401/403 подряд с одного IP — перебор паролей или токенов; приоритет 8.
- Скрейпинг листингов. Последовательный обход
?page=1,2,3…быстрее человека. - Всплеск ошибок 5xx во время наплыва — сервису плохо; приоритет 9.
- Порог и «здоровье» удобно вынести на HTTP-монитор.
- Атаку видно на старте, а не по счёту за трафик в конце месяца.
- Меньше ложных тревог. Доля клиента отличает реальный спайк от вашего же успешного запуска.
- Тротлинг встроен — во время атаки телефон не звенит без остановки.