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 verso la cam, invia una richiesta HTTP, la cam analizza, instrada, esegue l'handler e poi scrive una risposta di ritorno.

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.