Ga naar hoofdinhoud

Te veel verzoeken: wat de 429 vertelt

Hier bouw je op verder

Een bezoeker die over je limiet uit les 4 gaat, krijgt 429. In dat antwoord staat nog iets meer, en je server kan ook al eerder laten weten hoeveel verzoeken er nog over zijn. Een eerlijk script, zoals een app die je gastenboek uitleest, kan daar rekening mee houden.

Het antwoord bij een 429​

Zet deze regel onderaan test_verzoeken.py uit les 3:

print(antwoord.json())

Draai het script. De laatste regel is het antwoord op verzoek 20:

{'error': 'Rate limit exceeded: 5 per 1 minute'}

slowapi zegt welke grens het was: vijf per minuut. Wanneer het weer mag, staat er niet bij.

Meer vertellen met headers_enabled​

slowapi kan bij elk antwoord een paar headers meesturen: regels boven het antwoord, die je in het tabblad Netwerk van je browser ook ziet. Zet ze aan met headers_enabled=True:

from fastapi import FastAPI, Request, Response
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, headers_enabled=True)

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, response: Response):
return {"bericht": "Hallo wereld!"}
  1. Regel 1:

    Response komt erbij in de import.

  2. Regel 6:

    headers_enabled=True: stuur bij elk antwoord mee hoe het met de limiet staat.

  3. Regel 14:

    Het endpoint krijgt een parameter response: Response. Op dat antwoord zet slowapi de headers.

Kijk wat er meekomt​

Maak een script headers.py dat zes verzoeken stuurt en bij elk twee headers laat zien:

import httpx

adres = "http://127.0.0.1:8000/"

for nummer in range(6):
antwoord = httpx.get(adres)
over = antwoord.headers.get("x-ratelimit-remaining")
wacht = antwoord.headers.get("retry-after")
print(f"Verzoek {nummer + 1}: {antwoord.status_code}, nog {over} over, wacht {wacht} seconden")
  1. Regel 7:

    x-ratelimit-remaining: hoeveel verzoeken er deze minuut nog over zijn.

  2. Regel 8:

    retry-after: hoeveel seconden een bezoeker moet wachten tot de telling weer begint.

Start je server opnieuw, zodat de telling bij nul begint, en draai het script:

Verzoek 1: 200, nog 4 over, wacht 60 seconden
Verzoek 2: 200, nog 3 over, wacht 60 seconden
Verzoek 3: 200, nog 2 over, wacht 60 seconden
Verzoek 4: 200, nog 1 over, wacht 60 seconden
Verzoek 5: 200, nog 0 over, wacht 60 seconden
Verzoek 6: 429, nog 0 over, wacht 60 seconden

Bij een 200 hoef je niet te wachten: retry-after zegt dan alleen wanneer de telling opnieuw begint. Pas bij een 429 moet je die tijd afwachten. Een eerlijk script kijkt dus naar x-ratelimit-remaining en stopt bij nul, of wacht na een 429 zo lang als retry-after zegt.

Draai je het script binnen dezelfde minuut nog een keer, dan krijg je al bij verzoek 1 een 429, met nog 0 over en een wachttijd van hoogstens 60 seconden: de telling van de vorige keer loopt nog.

Er gaat iets mis​

Het script print bij elk verzoek nog None over, wacht None seconden.

Oorzaak: je server heeft headers_enabled=True nog niet, bijvoorbeeld omdat hij nog de code uit les 4 draait. Dan stuurt hij de headers niet mee, en headers.get geeft None als een header ontbreekt.

Oplossing: zet headers_enabled=True in de Limiter en response: Response in je endpoint, en sla main.py op.

Na headers_enabled=True geeft elk verzoek ineens 500 Internal Server Error, ook het eerste. In de terminal van je server eindigt de foutmelding met:

Exception: parameter `response` must be an instance of starlette.responses.Response

Oorzaak: je endpoint heeft geen parameter response: Response. slowapi wil de headers op het antwoord zetten, maar heeft er dan geen.

Oplossing: zet response: Response bij de parameters van elk endpoint met een limiet, en Response in de import:

# FOUT
async def root(request: Request):

# GOED
async def root(request: Request, response: Response):

Opdrachten​

Opdracht 1: Predict - Na drie verzoeken​

De limiet is 5/minute, met headers_enabled=True. Je stuurt drie verzoeken.

Vraag: wat staat er bij het derde antwoord in x-ratelimit-remaining?

Antwoord

2. Na drie van de vijf verzoeken zijn er nog twee over.

Opdracht 2: Make - Een script dat op tijd stopt​

Pas headers.py aan zodat het uit zichzelf stopt zodra er nog 0 verzoeken over zijn, in plaats van door te gaan tot een 429.

Tip

Met break stop je een for-loop halverwege.

Antwoord
import httpx

adres = "http://127.0.0.1:8000/"

for nummer in range(6):
antwoord = httpx.get(adres)
over = antwoord.headers.get("x-ratelimit-remaining")
print(f"Verzoek {nummer + 1}: {antwoord.status_code}, nog {over} over")
if over == "0":
print("Limiet bereikt, ik stop")
break

De header is tekst, dus je vergelijkt met "0" en niet met 0. Het script stopt na het vijfde verzoek, en krijgt geen 429.

Zo ziet je main.py er nu uit
from fastapi import FastAPI, Request, Response
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, headers_enabled=True)

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, response: Response):
return {"bericht": "Hallo wereld!"}

De limiet vertelt nu bij elk antwoord hoeveel er nog over is.

Door naar les 6: in de praktijk.