Te veel verzoeken: een limiet met slowapi
In les 3 beantwoordde je server elk verzoek. Je kunt niet voorkomen dat iemand veel verzoeken stuurt. Wat je wél kunt, is weigeren om ze allemaal te beantwoorden. Krijgt één bezoeker meer dan een paar verzoeken per minuut, dan is dat geen mens die een pagina leest, maar een script.
Een grens op het aantal verzoeken per bezoeker heet een rate limit, een verzoeklimiet. Hij stopt één computer die te veel vraagt, of die zijn verzoeken nu na elkaar of tegelijk stuurt. Tegen een DDoS van duizenden computers helpt hij minder, want elk apparaat blijft onder de grens; wat daartegen helpt, zie je in les 6.
slowapi installeren
De module slowapi houdt de telling voor je bij. Installeer hem in de terminal
van VS Code:
python -m pip install slowapi
De limiet in je server
from fastapi import FastAPI, Request
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
app = FastAPI()
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
@app.get("/")
@limiter.limit("5/minute")
async def root(request: Request):
return {"bericht": "Hallo wereld!"}
- Regel 6:
De
Limitertelt de verzoeken.get_remote_addresszegt: tel per computer, aan de hand van zijn adres. Voor jouw script is dat127.0.0.1. - Regel 9-10:
Je hangt de limiter aan je app, en zegt wat er moet gebeuren als iemand over de grens gaat: het antwoord
429 Too Many Requests. - Regel 13:
@limiter.limit("5/minute")boven een endpoint betekent: hoogstens vijf verzoeken per minuut per computer. - Regel 14:
Door de limiter heeft de functie nu
request: Requestnodig. Uit dat verzoek haaltslowapihet adres om te tellen.
Het endpoint zelf doet niets anders dan eerst: de limiet staat erboven, als een portier voor de deur.
Doe de test opnieuw
Start je server opnieuw en draai test_verzoeken.py uit
les 3:
Verzoek 1: status 200
Verzoek 2: status 200
Verzoek 3: status 200
Verzoek 4: status 200
Verzoek 5: status 200
Verzoek 6: status 429
Verzoek 7: status 429
en zo verder tot verzoek 20, met aan het eind Gelukt: 5 van de 20. De eerste
vijf krijgen antwoord, de rest weigert je server met 429. Hoeveel het script
ook stuurt, je server doet nog maar vijf keer echt werk per minuut. Een bezoeker
die één keer je pagina opvraagt, merkt er niets van.
Draai je het script meteen nog een keer, dan krijg je twintig keer 429: de
minuut is nog niet om, en je hebt je vijf al gehad.
Er gaat iets mis
Je server start niet, en in de terminal eindigt de foutmelding met:
Exception: No "request" or "websocket" argument on function "<function root at 0x…>"
Oorzaak: je endpoint heeft geen request: Request als parameter, terwijl
@limiter.limit(...) erboven staat. slowapi controleert dat al bij het
starten: zonder request weet hij niet van wie een verzoek komt. Het getal
achter 0x verschilt per keer.
Oplossing: zet request: Request tussen de haakjes van je functie:
# FOUT
@app.get("/")
@limiter.limit("5/minute")
async def root():
return {"bericht": "Hallo wereld!"}
# GOED
@app.get("/")
@limiter.limit("5/minute")
async def root(request: Request):
return {"bericht": "Hallo wereld!"}
Opdrachten
Opdracht 1: Predict - Wat zie je?
Je hebt deze limiet op een endpoint gezet:
@app.get("/tijd")
@limiter.limit("3/minute")
async def tijd(request: Request):
return {"bericht": "Het is nu laat"}
Je vraagt /tijd vier keer snel achter elkaar op.
Vraag: welke statuscodes krijg je terug?
Tip
De limiet is drie per minuut. Tel hoeveel verzoeken er antwoord krijgen voordat de grens bereikt is.
Antwoord
200
200
200
429
De eerste drie passen binnen de limiet, het vierde weigert de server met 429 Too Many Requests.
Opdracht 2: Modify - Tien per minuut
Zet een limiet van 10/minute op je startpagina. Wacht een minuut, zodat de
oude telling weg is, en draai test_verzoeken.py. Hoeveel verzoeken krijgen
200, en hoeveel 429?
Antwoord
Tien krijgen 200, de andere tien 429. Aan het eind staat er
Gelukt: 10 van de 20.
Opdracht 3: Investigate - Een strengere grens
Verander de limiet in "2/minute" en draai het script opnieuw. Waarom zou een
limiet van twee per minuut op een echte site geen goed idee zijn?
Tip
Denk aan een bezoeker die na het lezen van je startpagina op een link klikt en terugkomt.
Antwoord
Nu krijgen maar twee verzoeken 200 en de rest 429. Op een echte site is dat
te streng: een gewone bezoeker die een paar pagina's bekijkt of even
terugklikt, zit zo aan twee verzoeken, en dan weigert je server hem. Een limiet
moet hoog genoeg zijn voor een mens, en laag genoeg om een script te stoppen.
Zo ziet je main.py er nu uit
from fastapi import FastAPI, Request
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
app = FastAPI()
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
@app.get("/")
@limiter.limit("5/minute")
async def root(request: Request):
return {"bericht": "Hallo wereld!"}
De startpagina heeft nu een limiet van vijf per minuut.
Door naar les 5: wat de 429 vertelt.