10.1. Twój pierwszy punkt końcowy

Zanim kamera będzie mogła zrobić cokolwiek interesującego, reszta sieci musi być w stanie ją osiągnąć. Najtańszą rzeczą, która dowodzi, że serwer działa, jest jednotrasowy punkt końcowy HTTP zwracający trochę danych 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)

Uruchom go w IDE. Z dowolnego innego komputera w sieci LAN otwórz http://<cam-ip>/status. Przeglądarka pokaże:

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

Liczniki to wartości zastępcze – nic ich jeszcze nie modyfikuje – ale żądanie przeszło przez sieć, kamera je przekierowała, uruchomiła procedurę obsługi i odesłała JSON z powrotem.

10.1.1. Co robi każda linia

Jedna instancja microdot.Microdot na skrypt. Instancja jest właścicielem tablicy routingu, procedur obsługi błędów oraz cyklu życia (start, obsługa, zatrzymanie). Duże aplikacje dzielą się na kilka modułów Python, ale nadal współdzielą jeden obiekt app.

@app.get('/status') to dekorator trasy. Używamy tutaj tylko microdot.Microdot.get(); post(), put() oraz delete() pojawiają się na późniejszych stronach, gdy kamera zaczyna przyjmować zapisy.

Każda procedura obsługi trasy jest korutyną asyncio i otrzymuje żądanie jako swój pierwszy argument. Procedura obsługi nie musi używać request – ta go ignoruje – ale parametr jest zawsze obecny, więc sygnatura pozostaje spójna.

Zwrócenie słownika to najkrótszy sposób na wysłanie danych JSON. Microdot automatycznie serializuje słownik do JSON i ustawia w odpowiedzi nagłówek Content-Type: application/json. Zwrócenie łańcucha znaków wysyła text/plain. Jawne zwrócenie obiektu microdot.Response to forma rozbudowana – potrzebna, gdy treść jest binarna lub gdy odpowiedź wymaga niestandardowych nagłówków.

app.run(host='0.0.0.0', port=80) uruchamia serwer. 0.0.0.0 oznacza nasłuchuj na każdym interfejsie, który ma kamera – zarówno na przewodowym ethernecie, jak i na wifi STA, jeśli oba są aktywne. Port 80 jest domyślnym portem HTTP, więc przeglądarki nie muszą wpisywać numeru portu.

10.1.2. Jedno żądanie, od początku do końca

Telefon otwiera połączenie TCP z kamerą, wysyła żądanie HTTP, kamera je analizuje, przekierowuje, uruchamia procedurę obsługi, a następnie odsyła odpowiedź z powrotem.

Telefon otwiera połączenie TCP, zapisuje linię żądania i nagłówki, po czym czeka. Kamera odczytuje bajty z gniazda, analizuje je do postaci obiektu microdot.Request, dopasowuje ścieżkę i metodę do tablicy routingu, oczekuje na korutynę procedury obsługi, serializuje to, co ona zwróciła, zapisuje linię statusu, nagłówki i treść z powrotem do gniazda, a następnie zamyka połączenie (domyślnie HTTP/1.0) lub poddaje je recyklingowi (HTTP/1.1 z Connection: keep-alive). Cała wymiana trwa mniej więcej tyle, co czas obiegu w sieci plus to, co zrobiła procedura obsługi.

10.1.3. Uwaga o blokowaniu

run() jest blokujące – nigdy nie zwraca sterowania, dopóki serwer się nie zatrzyma. To w porządku dla serwera jednego przeznaczenia. Aplikacja, która dodatkowo przechwytuje ramki lub uruchamia inne korutyny, używa zamiast tego start_server() wewnątrz asyncio.run(), dzięki czemu serwer HTTP może współdzielić pętlę z całą resztą.

Aplikacja odpowiada na jeden URL.