# Полное руководство по DRF Throttling: зачем он нужен и как настроить, применить и кастомизировать Наличие `limit_req` в nginx‑обратном прокси безусловно эффективен. Однако, когда хочется задать **разные политики для разных представлений/действий** (например, логин – 5 раз в минуту, загрузка – 20 раз в день, просмотр – 1000 раз на пользователя), полагаться только на сервер/инфраструктуру становится рискованно. Throttling в DRF позволяет **создавать ограничения, адаптированные к особенностям эндпоинта**, что делает его обязательным знанием. --- ## Зачем нужен Throttling Throttling в DRF, как и разрешения (permissions), решает вопрос **разрешить ли запрос**, но в отличие от них он **временный**. Документация описывает его как «временное состояние, контролирующее скорость запросов клиента к API». Практическая необходимость выглядит так: - **Смягчение злоупотреблений/атак**: перебор логина, спам‑запросы, сканирование, простые DoS. - **Защита ресурсов/затрат**: загрузки, внешние API‑вызовы, тяжёлые запросы, вызовы генеративного ИИ. - **Справедливое использование**: предотвращение доминирования одного пользователя/ключа. - **Кодирование политики**: правила вроде «этот API – N запросов в минуту» управляются в коде, а не в инфраструктуре. Важно: throttling может применяться **глобально** или **на уровне конкретного представления/действия** – именно здесь nginx может оказаться недостаточным. --- ## Как работает DRF Throttling (основные концепции) ### 1) Строка rate Ограничения задаются в виде строк, например `"100/day"`, `"60/min"`. ### 2) Кому ограничение - `UserRateThrottle`: для аутентифицированных пользователей – по `user id`, для анонимных – по IP. - `AnonRateThrottle`: только анонимные запросы ограничиваются по IP. - `ScopedRateThrottle`: применяет политику по **scope** (например, `uploads`). IP‑идентификация берётся из `X-Forwarded-For` или `REMOTE_ADDR`; при работе за прокси важно корректно настроить `NUM_PROXIES`. ### 3) Хранение состояния Базовый механизм использует **Django cache backend**. В однопроцессном окружении `LocMemCache` подходит, но в многопроцессных/мульти‑репликационных системах необходим общий кеш, например Redis. --- ## Глобальная настройка: быстрый старт ```python REST_FRAMEWORK = { "DEFAULT_THROTTLE_CLASSES": [ "rest_framework.throttling.AnonRateThrottle", "rest_framework.throttling.UserRateThrottle", ], "DEFAULT_THROTTLE_RATES": { "anon": "100/day", "user": "1000/day", }, } ``` После такой конфигурации «базовая политика» применяется ко всем API‑эндпоинтам. При превышении лимита DRF возвращает **HTTP 429 (Too Many Requests)**. --- ## Применение к конкретному представлению ### 1) Класс‑основанное представление (APIView) ```python from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.throttling import UserRateThrottle class ExpensiveView(APIView): throttle_classes = [UserRateThrottle] def get(self, request): return Response({"ok": True}) ``` ### 2) Функциональное представление (@api_view) ```python from rest_framework.decorators import api_view, throttle_classes from rest_framework.throttling import UserRateThrottle from rest_framework.response import Response @api_view(["GET"]) @throttle_classes([UserRateThrottle]) def ping(request): return Response({"pong": True}) ``` ### 3) ViewSet – только для конкретного действия (@action) ```python from rest_framework.decorators import action from rest_framework.throttling import UserRateThrottle from rest_framework.viewsets import ViewSet from rest_framework.response import Response class ItemViewSet(ViewSet): @action(detail=True, methods=["post"], throttle_classes=[UserRateThrottle]) def purchase(self, request, pk=None): return Response({"purchased": pk}) ``` Атрибут `throttle_classes` на уровне действия имеет приоритет над настройками на уровне ViewSet. --- ## ScopedRateThrottle: политика по «области» (рекомендовано) Scoped throttling позволяет разделить правила по смысловым областям, например `login`, `uploads`, `search`. ```python REST_FRAMEWORK = { "DEFAULT_THROTTLE_CLASSES": [ "rest_framework.throttling.ScopedRateThrottle", ], "DEFAULT_THROTTLE_RATES": { "login": "5/min", "uploads": "20/day", "search": "60/min", }, } ``` В представлении задаём только `throttle_scope`: ```python from rest_framework.views import APIView from rest_framework.response import Response class LoginView(APIView): throttle_scope = "login" def post(self, request): return Response({"ok": True}) ``` DRF создаёт уникальный ключ из `scope` + `user id` или `IP` и хранит счётчик в кеше. --- ## Создание собственного Throttle: ключ – ключевой момент Хотя встроенные throttles покрывают большинство случаев, в реальных проектах часто требуется: - Ограничение по комбинации IP + username. - Ограничение по API‑ключу. - Ограничение по заголовку/тенанту/организации. - Использование другого кеша (например, Redis‑кластер). ### 1) Самый распространённый способ: наследование SimpleRateThrottle ```python from rest_framework.throttling import SimpleRateThrottle class LoginBurstThrottle(SimpleRateThrottle): scope = "login" def get_cache_key(self, request, view): username = (request.data.get("username") or "").lower().strip() ident = self.get_ident(request) # IP‑идентификатор if not username: return None return f"throttle_login:{ident}:{username}" ``` Регистрация в настройках: ```python REST_FRAMEWORK = { "DEFAULT_THROTTLE_CLASSES": [ "path.to.LoginBurstThrottle", ], "DEFAULT_THROTTLE_RATES": { "login": "5/min", }, } ``` ### 2) Использование другого кеша (cache attribute) ```python from django.core.cache import caches from rest_framework.throttling import AnonRateThrottle class CustomCacheAnonThrottle(AnonRateThrottle): cache = caches["alternate"] ``` --- ## Что важно знать перед деплоем ### 1) Проблема одинакового IP за прокси IP‑идентификация основана на `X-Forwarded-For`/`REMOTE_ADDR`. Если `NUM_PROXIES` неверно настроен, все пользователи могут восприниматься как один. ### 2) LocMemCache слаб в многопроцессных/мульти‑серверных средах Кеш локальный для процесса, поэтому счётчики могут расходиться. Для надёжной работы используйте Redis или аналогичный общий кеш. ### 3) Условие гонки (race condition) Встроенный механизм может позволить несколько запросов пройти сверх лимита при высокой конкуренции. Для критичных операций (платёж, купон) рассмотрите атомарный счётчик, например Redis `INCR + EXPIRE`. ### 4) Клиентская дружелюбность: 429 и Retry-After По умолчанию DRF возвращает 429. Реализовав `wait()` в кастомном throttle, можно добавить заголовок `Retry-After`. --- ## Итоги: nginx + DRF throttling – двойная защита ![Роботный клуб: входной контроль и 429‑знак](/media/editor_temp/6/c96bb1d6-7cde-48a0-98aa-07396570391d.png) - **nginx** – первый щит, отсекающий массовый трафик и атаки. - **DRF throttling** – точечные ограничения, учитывающие смысл и стоимость эндпоинта. Особенно полезно ограничивать каждый эндпоинт индивидуально: DRF позволяет это сделать быстро и без изменения инфраструктуры.