10.1. Primul tău endpoint

Înainte ca aceasta cameră să poată face ceva interesant, restul rețelei trebuie să o poată accesa. Cel mai ieftin lucru care dovedește că serverul este activ este un endpoint HTTP cu o singură rută care returnează niște 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)

Rulează-l în IDE. De pe orice altă mașină din LAN deschide http://<cam-ip>/status. Browserul afișează:

{"frames": 0, "triggers": 0}

Contoarele sunt locțiitoare – nimic nu le atinge încă – dar cererea a traversat rețeaua, camera a direcționat-o, a rulat un handler și a trimis JSON înapoi.

10.1.1. Ce face fiecare linie

O singură instanță microdot.Microdot per script. Instanța deține tabelul de rutare, handlerele de erori și ciclul de viață (pornire, servire, oprire). Aplicațiile mari se împart în mai multe module Python, dar tot partajează un singur obiect app.

@app.get('/status') este decoratorul de rută. Aici folosim doar microdot.Microdot.get(); post(), put() și delete() apar în paginile ulterioare, când camera începe să accepte scrieri.

Fiecare handler de rută este o corutină asyncio și primește cererea ca prim argument. Handlerul nu este obligat să folosească request – acesta o ignoră – dar parametrul este întotdeauna prezent, astfel încât semnătura să fie consecventă.

Returnarea unui dict este cea mai scurtă modalitate de a trimite JSON. Microdot serializează automat dict-ul în JSON și setează Content-Type: application/json în răspuns. Returnarea unui string trimite text/plain. Returnarea explicită a unui microdot.Response este forma lungă – necesară atunci când corpul este binar sau când răspunsul are nevoie de anteturi personalizate.

app.run(host='0.0.0.0', port=80) pornește serverul. 0.0.0.0 înseamnă ascultă pe fiecare interfață pe care o are camera – atât ethernetul cablat, cât și wifi STA, dacă ambele sunt active. Portul 80 este implicit pentru HTTP, așa că browserele nu trebuie să tasteze un număr de port.

10.1.2. O cerere, de la cap la coadă

Telefonul deschide o conexiune TCP către cameră, trimite o cerere HTTP, camera o analizează, o direcționează, rulează handlerul, apoi scrie un răspuns înapoi.

Telefonul deschide o conexiune TCP, scrie linia de cerere și anteturile, apoi așteaptă. Camera citește octeții de pe socket, îi analizează într-un obiect microdot.Request, potrivește calea și metoda cu tabelul de rutare, așteaptă corutina handlerului, serializează ceea ce a returnat aceasta, scrie o linie de status, anteturi și corp înapoi pe socket, apoi închide conexiunea (implicit la HTTP/1.0) sau o reciclează (HTTP/1.1 cu Connection: keep-alive). Întregul schimb durează aproximativ cât round-trip-ul de rețea plus ceea ce a făcut handlerul.

10.1.3. O notă despre blocare

run() este blocant – nu returnează niciodată până când serverul nu se oprește. Acest lucru este în regulă pentru un server cu un singur scop. O aplicație care captează și cadre sau rulează alte corutine folosește în schimb start_server() în interiorul unui asyncio.run(), astfel încât serverul HTTP să poată partaja bucla cu tot restul.

Aplicația răspunde la un singur URL.