10.1. Il tuo primo endpoint#
Prima che la cam possa fare qualcosa di interessante, il resto della rete deve essere in grado di raggiungerla. La cosa più semplice che dimostra che il server è attivo è un endpoint HTTP a una sola route che restituisce un po” di JSON:
from microdot import Microdot
app = Microdot()
frame_count = 0
trigger_count = 0
@app.get('/status')
async def status(request):
return {'frames': frame_count, 'triggers': trigger_count}
app.run(host='0.0.0.0', port=80)
Eseguilo nell’IDE. Da qualsiasi altra macchina della LAN apri http://<cam-ip>/status. Il browser mostra:
{"frames": 0, "triggers": 0}
I contatori sono segnaposto – per ora niente li tocca – ma la richiesta ha attraversato la rete, la cam l’ha instradata, ha eseguito un handler e ha rispedito indietro il JSON.
10.1.1. Cosa fa ogni riga#
Un’istanza di microdot.Microdot per script. L’istanza possiede la tabella di routing, gli handler degli errori e il ciclo di vita (avvio, gestione, arresto). Le app grandi si suddividono in più moduli Python ma condividono comunque un unico oggetto app.
@app.get('/status') è il decoratore della route. Qui usiamo solo microdot.Microdot.get(); post(), put() e delete() compaiono nelle pagine successive, quando la cam inizia ad accettare scritture.
Ogni handler di route è una coroutine asyncio e riceve la richiesta come primo argomento. L’handler non è obbligato a usare request – questo lo ignora – ma il parametro è sempre presente, così la firma rimane coerente.
Restituire un dict è il modo più breve per inviare JSON. Microdot serializza automaticamente il dict in JSON e imposta Content-Type: application/json sulla risposta. Restituire una stringa invia text/plain. Restituire esplicitamente un microdot.Response è la forma estesa – necessaria quando il corpo è binario o quando la risposta richiede header personalizzati.
app.run(host='0.0.0.0', port=80) avvia il server. 0.0.0.0 significa ascolta su ogni interfaccia di cui la cam dispone – sia l’ethernet cablata sia il wifi STA, se entrambi sono attivi. La porta 80 è quella predefinita per HTTP, così i browser non devono digitare un numero di porta.
10.1.2. Una richiesta, dall’inizio alla fine#
Il telefono apre una connessione TCP, scrive la riga di richiesta e gli header, e attende. La cam legge i byte dal socket, li analizza trasformandoli in un oggetto microdot.Request, confronta il percorso e il metodo con la tabella di routing, attende la coroutine dell’handler, serializza qualunque cosa essa abbia restituito, scrive una riga di stato, gli header e il corpo nuovamente lungo il socket, quindi chiude la connessione (predefinito in HTTP/1.0) o la riutilizza (HTTP/1.1 con Connection: keep-alive). L’intero scambio dura più o meno quanto il round-trip di rete più ciò che ha fatto l’handler.
10.1.3. Una nota sul blocco#
run() è bloccante – non ritorna mai finché il server non si arresta. Va bene per un server monouso. Un’app che cattura anche frame o esegue altre coroutine usa invece start_server() all’interno di un asyncio.run(), così il server HTTP può condividere il loop con tutto il resto.
L’app risponde a un URL.