HTML van een bezoeker: het gastenboek van deze reeks
In het htmx-recept Een formulier versturen
zette je escape(naam) in het antwoord, zodat een naam als <b>Sara</b> niet
vet werd. Straks haal je die escape weg en kijk je wat er dan gebeurt. Eerst
zet je het gastenboek klaar waarop je dat doet.
Maak voor deze reeks een nieuwe map veiligheid-xss, naast je eigen
project, en open die in VS Code. De server, de scripts en de database 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.
Een gastenboek met een f-string
Dit gastenboek zet de berichten niet in een template, maar in een f-string,
en het antwoord na het versturen heeft geen escape. Het is kleiner dan je
gastenboek uit de basis: zonder accounts en zonder wachtwoordveld. Wie een
bericht schrijft, typt alleen een naam.
De pagina zelf is een los bestand static/gastenboek.html:
<!doctype html>
<html>
<head>
<script src="/static/js/htmx.min.js" defer></script>
</head>
<body>
<form hx-post="/gastenboek" hx-target="#antwoord">
<input type="text" name="naam" placeholder="Je naam" required>
<input type="text" name="bericht" placeholder="Je bericht" required>
<button type="submit">Verstuur</button>
</form>
<p id="antwoord"></p>
<div id="lijst" hx-get="/berichten/lijst" hx-trigger="load, every 5s"></div>
</body>
</html>
Door load haalt htmx de lijst meteen op als de pagina geladen is, en na de
komma komt every 5s erbij: daarna elke vijf seconden opnieuw.
Zet htmx.min.js uit Zonder herladen in static/js/.
De endpoints staan in main.py:
import time
from fastapi import FastAPI, Form
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from sqlitedict import SqliteDict
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
@app.post("/gastenboek")
async def gastenboek_opslaan(naam: str = Form(...), bericht: str = Form(...)):
with SqliteDict("gastenboek.db") as db:
db[f"bericht_{time.time_ns()}"] = {"naam": naam, "bericht": bericht}
db.commit()
return HTMLResponse(f"Bedankt, {naam}. Je bericht staat in het gastenboek.")
@app.get("/berichten/lijst")
async def berichten_lijst():
regels = ""
with SqliteDict("gastenboek.db") as db:
for bericht in db.values():
regels += f"<li>{bericht['naam']}: {bericht['bericht']}</li>"
return HTMLResponse(f"<ul>{regels}</ul>")
- Regel 16:
Het antwoord na het versturen, zoals in het recept Een formulier versturen, maar zonder
escapeom de naam. - Regel 20-24:
Elk bericht wordt een
<li>, met een f-string. De naam en het bericht komen erin zoals de bezoeker ze typte.
Starten
Doe alles in deze reeks uitsluitend op je eigen server op 127.0.0.1. HTML
invullen op de site van een ander om te kijken wat er gebeurt, is een aanval op
die site en op de bezoekers ervan, en strafbaar.
Start je server met fastapi dev main.py en open
http://127.0.0.1:8000/static/gastenboek.html. Vul als naam Sara in en als
bericht hoi, en klik op Verstuur.
Zo ziet je main.py er nu uit
import time
from fastapi import FastAPI, Form
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from sqlitedict import SqliteDict
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
@app.post("/gastenboek")
async def gastenboek_opslaan(naam: str = Form(...), bericht: str = Form(...)):
with SqliteDict("gastenboek.db") as db:
db[f"bericht_{time.time_ns()}"] = {"naam": naam, "bericht": bericht}
db.commit()
return HTMLResponse(f"Bedankt, {naam}. Je bericht staat in het gastenboek.")
@app.get("/berichten/lijst")
async def berichten_lijst():
regels = ""
with SqliteDict("gastenboek.db") as db:
for bericht in db.values():
regels += f"<li>{bericht['naam']}: {bericht['bericht']}</li>"
return HTMLResponse(f"<ul>{regels}</ul>")
Naast main.py staan static/gastenboek.html en static/js/htmx.min.js. Dit
is de zwakke versie; in les 3 komt er escape in.
Er gaat iets mis
Het formulier verstuurt niets
Na Verstuur staat er ?naam=Sara&bericht=hoi in de adresbalk, en de lijst
blijft leeg.
Oorzaak: htmx is niet geladen. Zonder htmx kent de browser hx-post en
hx-get niet, en verstuurt hij het formulier als gewone GET naar dezelfde
pagina.
Oplossing: kijk in het tabblad Netwerk of htmx.min.js een 200
kreeg. Een 404 betekent dat het bestand niet in static/js/ staat.
Meer uitleg: Zonder herladen met htmx
Directory 'static' does not exist
De server start niet, en de foutmelding in de terminal eindigt met:
RuntimeError: Directory 'static' does not exist
Oorzaak: app.mount zoekt de map static naast de plek waar je de server
startte. Die map ontbreekt, of je terminal staat in een andere map.
Oplossing: maak de map static naast main.py, met gastenboek.html
erin, en start de server vanuit de map veiligheid-xss.
Address already in use
Je browser toont {"detail":"Not Found"} in plaats van het formulier. In de
terminal van je nieuwe server eindigt de melding op Windows met:
[WinError 10048] Elk socketadres (protocol/netwerkadres/poort) kan normaal slechts één keer worden gebruikt
Op macOS en Linux staat er Address already in use.
Oorzaak: de server van de vorige reeks draait nog op poort 8000, in een
ander venster van VS Code. Je nieuwe server kon niet starten, en je browser
vroeg gastenboek.html aan de oude, die dat bestand niet heeft.
Oplossing: stop de oude server met Ctrl+C in zijn terminal, en start deze opnieuw.
Een rode regel favicon.ico met 404
In het tabblad Netwerk en in de terminal van je server staat
GET /favicon.ico met 404 Not Found.
Oorzaak: de browser vraagt vanzelf om een icoontje voor het tabblad, en deze server heeft er geen.
Oplossing: niets; het heeft niets met htmx of je gastenboek te maken.
Opdrachten
Opdracht 1: Run - Een tweede bezoeker
Open dezelfde pagina in een privévenster, als een tweede bezoeker, en schrijf
daar als Noor een bericht. Wanneer verschijnt het in het eerste venster, en
hoe zie je in de terminal dat er nu twee bezoekers zijn?
Antwoord
Binnen vijf seconden, zonder dat je het eerste venster herlaadt: daar haalt
htmx de lijst ook elke vijf seconden op. In de terminal komen er nu elke vijf
seconden twee regels GET /berichten/lijst bij, één per venster.
Door naar les 2: tekst of HTML?.