Ga naar hoofdinhoud

Wat je server laat zien: een gastenboek met sessies

Waar je werkt

Maak voor deze reeks een nieuwe map veiligheid-zichtbaar, naast je eigen project, en open die in VS Code. De server, de scripts en de databases van deze reeks komen daarin. Zo blijft je eigen gastenboek heel. Aan het eind van de reeks pas je het geleerde toe op je eigen project.

Maak in die map een virtual environment en installeer de packages; hoe dat gaat staat bij Een map per reeks.

Je zet de server van deze reeks klaar en start hem. In de lessen erna kijk je wat hij over zichzelf vertelt, en aan wie.

Een server om mee te oefenen​

Zet dit main.py in de map. Het is een klein gastenboek met sessies, kleiner dan dat uit Onthouden op de server: sessies: zonder accounts, en je sessie begint bij je eerste bericht. Onderaan staat ook het endpoint GET /sessies uit opdracht 3 van die les, dat je daar maakte om in je sessies te kijken.

import secrets
import time

from fastapi import Cookie, FastAPI, Form
from fastapi.responses import JSONResponse
from sqlitedict import SqliteDict

app = FastAPI()

@app.post("/bericht")
async def bericht_plaatsen(
naam: str = Form(...),
bericht: str = Form(...),
sessie_id: str = Cookie(default=""),
):
if not sessie_id:
sessie_id = secrets.token_hex(16)
sleutel = f"bericht_{time.time_ns()}"
with SqliteDict("gastenboek.db") as db:
db[sleutel] = {"naam": naam, "bericht": bericht}
db.commit()
with SqliteDict("sessies.db") as sessies:
mijn = sessies.get(sessie_id, {"naam": naam, "berichten": []})
mijn["naam"] = naam
mijn["berichten"].append(sleutel)
sessies[sessie_id] = mijn
sessies.commit()
antwoord = JSONResponse({"opgeslagen": sleutel})
antwoord.set_cookie(key="sessie_id", value=sessie_id, httponly=True, samesite="lax")
return antwoord

@app.get("/berichten")
async def berichten():
with SqliteDict("gastenboek.db") as db:
return list(db.values())

@app.get("/ik")
async def wie_ben_ik(sessie_id: str = Cookie(default="")):
with SqliteDict("sessies.db") as sessies:
mijn = sessies.get(sessie_id, {})
return {"naam": mijn.get("naam", "onbekend")}

@app.get("/sessies")
async def alle_sessies():
with SqliteDict("sessies.db") as sessies:
return dict(sessies)
  1. Regel 10-28:

    Een bericht plaatsen. Het bericht gaat in gastenboek.db, en in sessies.db onthoudt de server wie het schreef, onder het sessie-id uit de cookie.

  2. Regel 29:

    De cookie met het sessie-id. In Sessies stond er max_age bij; hier staan httponly en samesite, twee instellingen die de cookie afschermen. Wat ze doen, leer je later in Cookies afschermen: httponly.

  3. Regel 37-41:

    /ik zegt wie je bent: de naam uit je sessie, of onbekend als je server je sessie-id niet kent.

  4. Regel 43-46:

    Het endpoint uit opdracht 3 van Sessies. Het geeft alles uit sessies.db terug, met de sessie-id's erbij.

Start de server met fastapi dev main.py. In de terminal staan twee adressen:

Server started at http://127.0.0.1:8000
Documentation at http://127.0.0.1:8000/docs

Het eerste ken je. Het tweede bekijk je in de volgende stap.

Kijk of hij draait​

Alleen je eigen server

Bekijk en gebruik in deze reeks uitsluitend je eigen server op 127.0.0.1. Een endpoint van een ander gebruiken dat niet voor jou bedoeld is, is binnendringen, en strafbaar, ook als het "maar een testje" is.

Open http://127.0.0.1:8000/berichten in je browser, en daarna http://127.0.0.1:8000/ik.

Controlevraag
Stel: je browser heeft nog een cookie sessie_id van je eigen gastenboek, dat ook op 127.0.0.1 draaide. Je opent /ik van deze server. Wat zie je?
Kies wat jij denkt. Bij het goede antwoord zie je de uitleg.

Er gaat iets mis​

Path does not exist main.py​

fastapi dev main.py start niet, en zegt:

Path does not exist main.py

Oorzaak: de terminal staat in een andere map dan main.py, of je bewaarde main.py ergens anders dan in veiligheid-zichtbaar.

Oplossing: open de map veiligheid-zichtbaar in VS Code, zet main.py daarin, en start de server in de terminal van VS Code.

No module named 'sqlitedict'​

De server start niet, en onderaan staat:

ModuleNotFoundError: No module named 'sqlitedict'

Oorzaak: de packages staan niet in de virtual environment van deze map. Elke map heeft zijn eigen virtual environment.

Oplossing: installeer de packages in de terminal van VS Code, zoals bij Een map per reeks.

Opdrachten​

Opdracht 1: Investigate - Wanneer komt er een sessie?​

Kijk in bericht_plaatsen, regel 16 en 17. Wanneer maakt de server een nieuw sessie-id, en wanneer niet?

Tip

if not sessie_id is waar als de cookie leeg is.

Antwoord

Alleen als het verzoek geen cookie sessie_id meestuurt, bij je eerste bericht. Dan maakt secrets.token_hex(16) een sessie-id van 32 tekens, en zet de server het in een cookie. Bij je volgende bericht stuurt je browser die cookie mee, en gebruikt de server hetzelfde sessie-id.

Zo ziet je main.py er nu uit
import secrets
import time

from fastapi import Cookie, FastAPI, Form
from fastapi.responses import JSONResponse
from sqlitedict import SqliteDict

app = FastAPI()

@app.post("/bericht")
async def bericht_plaatsen(
naam: str = Form(...),
bericht: str = Form(...),
sessie_id: str = Cookie(default=""),
):
if not sessie_id:
sessie_id = secrets.token_hex(16)
sleutel = f"bericht_{time.time_ns()}"
with SqliteDict("gastenboek.db") as db:
db[sleutel] = {"naam": naam, "bericht": bericht}
db.commit()
with SqliteDict("sessies.db") as sessies:
mijn = sessies.get(sessie_id, {"naam": naam, "berichten": []})
mijn["naam"] = naam
mijn["berichten"].append(sleutel)
sessies[sessie_id] = mijn
sessies.commit()
antwoord = JSONResponse({"opgeslagen": sleutel})
antwoord.set_cookie(key="sessie_id", value=sessie_id, httponly=True, samesite="lax")
return antwoord

@app.get("/berichten")
async def berichten():
with SqliteDict("gastenboek.db") as db:
return list(db.values())

@app.get("/ik")
async def wie_ben_ik(sessie_id: str = Cookie(default="")):
with SqliteDict("sessies.db") as sessies:
mijn = sessies.get(sessie_id, {})
return {"naam": mijn.get("naam", "onbekend")}

@app.get("/sessies")
async def alle_sessies():
with SqliteDict("sessies.db") as sessies:
return dict(sessies)

Dit is de beginstand. In les 4 verandert de regel met app = FastAPI(), en gaat GET /sessies eruit.

Door naar les 2: de handleiding op /docs.