DRF Throttling (verzoeklimiet) volledig beheersen: waarom het nodig is en hoe je het instelt, toepast en aanpast

Het gebruik van limit_req in een nginx‑reverse proxy is duidelijk effectief. Maar wanneer je verschillende beleidsregels per view/actie wilt toepassen (bijv. login 5 keer per minuut, upload 20 keer per dag, view‑API 1000 keer per gebruiker), is het vertrouwen op alleen server‑ of infrastructuur‑instellingen niet ideaal. DRF‑throttling laat je applicatieniveau‑specifieke beperkingen maken die passen bij de aard van de endpoint, waardoor het een essentieel basiskennis is.


Waarom throttling nodig is



Throttling beslist of een verzoek wordt toegestaan, net als een permissie, maar het verschil is permanent (permissie) vs tijdelijk (verzoekfrequentie). De DRF‑documentatie beschrijft throttling als een “tijdelijke toestand die de snelheid van verzoeken van een client beheert”.

De praktische redenen zijn onder meer:

  • Misbruik/aanvallen verminderen: brute‑force login, spam, crawlen, eenvoudige DoS.
  • Kosten/bronbescherming: uploads, externe API‑aanroepen, zware queries, generatieve AI‑aanroepen.
  • Gelijke behandeling (fair use): voorkom dat één gebruiker of sleutel de middelen monopoliseert.
  • Code‑gebaseerde beleidsregels: “Deze API mag N keer per minuut” in plaats van alleen via infrastructuur.

Belangrijk: throttling kan globaal of per view/actie worden toegepast, iets waar nginx alleen vaak tekortschiet.


Hoe DRF Throttling werkt (kernconcepten)

1) Rate‑string

DRF gebruikt strings zoals "100/day" of "60/min".

2) Wie wordt beperkt (clientidentificatie)

  • UserRateThrottle: voor geauthenticeerde gebruikers op basis van user id, voor anonieme op basis van IP.
  • AnonRateThrottle: alleen anonieme verzoeken, gebaseerd op IP.
  • ScopedRateThrottle: toepast een scope‑gebaseerd beleid, bijvoorbeeld uploads.

IP‑detectie gebruikt X-Forwarded-For of REMOTE_ADDR; bij een proxy is NUM_PROXIES cruciaal.

3) Opslag in cache

De standaardimplementatie slaat de teller op in de Django‑cache. Bij een enkele worker/server is LocMemCache voldoende, maar bij meerdere workers of replicas is een gedeelde cache zoals Redis noodzakelijk.


Globale (global) instellingen: snel start



settings.py:

REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_CLASSES": [
        "rest_framework.throttling.AnonRateThrottle",
        "rest_framework.throttling.UserRateThrottle",
    ],
    "DEFAULT_THROTTLE_RATES": {
        "anon": "100/day",
        "user": "1000/day",
    },
}

Dit voegt een basisbeleid toe aan alle API‑punten. Bij overschrijding geeft DRF standaard een HTTP 429 (Too Many Requests) terug.


Throttling per view toepassen: verschillende limieten per endpoint

1) Class‑based view (APIView)

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) Function‑based view (@api_view)

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) Specifieke actie in een ViewSet (@action)

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})

Actie‑specifieke throttling heeft prioriteit boven de ViewSet‑niveau‑instelling.


ScopedRateThrottle gebruiken voor “endpoint‑specifieke” beleid (aanbevolen)

Scoped throttling maakt het mogelijk om beleid te scheiden op basis van een betekenisvolle scope (bijv. uploads, login).

settings.py:

REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_CLASSES": [
        "rest_framework.throttling.ScopedRateThrottle",
    ],
    "DEFAULT_THROTTLE_RATES": {
        "login": "5/min",
        "uploads": "20/day",
        "search": "60/min",
    },
}

View‑specifieke scope:

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 maakt een unieke sleutel van scope + user id of IP.


Een eigen Throttle maken: de sleutel bepalen is cruciaal

Hoewel ingebouwde throttles vaak voldoende zijn, komen er in de praktijk vaak de volgende eisen voor:

  • “Login beperken tot IP + gebruikersnaam”
  • “Per API‑sleutel beperken”
  • “Per header/tenant/organisatie beperken”
  • “Een andere cache (bijv. Redis‑cluster) gebruiken”

1) Meest voorkomende aanpak: SimpleRateThrottle overerven

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‑gebaseerde identificatie
        if not username:
            return None
        return f"throttle_login:{ident}:{username}"

Registratie in settings:

REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_CLASSES": [
        "path.to.LoginBurstThrottle",
    ],
    "DEFAULT_THROTTLE_RATES": {
        "login": "5/min",
    },
}

2) Een andere cache gebruiken (cache‑attribuut)

from django.core.cache import caches
from rest_framework.throttling import AnonRateThrottle

class CustomCacheAnonThrottle(AnonRateThrottle):
    cache = caches["alternate"]

Voor het uitrollen: belangrijke aandachtspunten

1) IP‑detectie in een proxy‑omgeving

Zonder correcte NUM_PROXIES kunnen alle gebruikers als één IP worden gezien.

2) LocMemCache is zwak bij meerdere workers/servers

Een gedeelde cache zoals Redis is essentieel voor consistente throttling.

3) Race‑condition bij hoge concurrentie

De ingebouwde implementatie kan bij hoge concurrentie een paar extra verzoeken toestaan. Voor kritieke scenario’s (bijv. betalingen) overweeg een atomische teller (Redis INCR + EXPIRE).

4) Klantvriendelijkheid: 429 en Retry‑After

DRF retourneert standaard 429. Door wait() te implementeren kun je een Retry-After‑header toevoegen.


Conclusie: zowel nginx als DRF‑throttling gebruiken

Robotclub toegangsbeperking en 429 neonbalk

  • nginx: blokkeert grote hoeveelheden verkeer of aanvallen direct aan de rand.
  • DRF‑throttling: past verfijnde, endpoint‑specifieke regels toe op applicatieniveau.

Voor “per view‑specifieke beperkingen” is DRF‑throttling het meest geschikt en blijft het consistent, zelfs als de serveromgeving verandert.