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#

The phone opens a TCP connection to the cam, sends an HTTP request, the cam parses, routes, runs the handler, then writes a response back.

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.