10.1. Ваша первая конечная точка

Прежде чем камера сможет делать что-то интересное, остальная сеть должна иметь возможность до неё дотянуться. Самое простое, что доказывает работоспособность сервера, – это однопутевая HTTP-конечная точка, которая возвращает немного 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)

Запустите его в IDE. С любого другого компьютера в локальной сети откройте http://<cam-ip>/status. Браузер покажет:

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

Счётчики – это заглушки (пока их никто не трогает), но запрос пересёк сеть, камера его маршрутизировала, выполнила обработчик и отправила JSON обратно.

10.1.1. Что делает каждая строка

Один экземпляр microdot.Microdot на скрипт. Этот экземпляр владеет таблицей маршрутизации, обработчиками ошибок и жизненным циклом (запуск, обслуживание, остановка). Большие приложения разбиваются на несколько модулей Python, но по-прежнему используют один общий объект app.

@app.get('/status') – это декоратор маршрута. Здесь мы используем только microdot.Microdot.get(); методы post(), put() и delete() появятся на следующих страницах, когда камера начнёт принимать записи.

Каждый обработчик маршрута – это корутина asyncio, которая получает запрос в качестве первого аргумента. Обработчик не обязан использовать request – этот его игнорирует, – но параметр всегда присутствует, чтобы сигнатура была единообразной.

Возврат словаря – это кратчайший способ отправить JSON. Microdot автоматически сериализует словарь в JSON и устанавливает Content-Type: application/json в ответе. Возврат строки отправляет text/plain. Явный возврат microdot.Response – это развёрнутая форма, нужная, когда тело двоичное или когда ответу требуются пользовательские заголовки.

app.run(host='0.0.0.0', port=80) запускает сервер. 0.0.0.0 означает слушать на каждом интерфейсе, который есть у камеры, – как на проводном Ethernet, так и на Wi-Fi STA, если оба активны. Порт 80 – это HTTP по умолчанию, поэтому браузерам не нужно вводить номер порта.

10.1.2. Один запрос от начала до конца

Телефон открывает TCP-соединение с камерой, отправляет HTTP- запрос, камера разбирает его, маршрутизирует, выполняет обработчик, затем записывает ответ обратно.

Телефон открывает TCP-соединение, записывает строку запроса и заголовки и ждёт. Камера считывает байты из сокета, разбирает их в объект microdot.Request, сопоставляет путь и метод с таблицей маршрутизации, ожидает корутину обработчика, сериализует всё, что она вернула, записывает строку статуса, заголовки и тело обратно в сокет, а затем закрывает соединение (по умолчанию HTTP/1.0) или переиспользует его (HTTP/1.1 с Connection: keep-alive). Весь обмен занимает примерно столько же времени, сколько сетевой круговой путь плюс то, что сделал обработчик.

10.1.3. Замечание о блокировке

run() является блокирующим – он никогда не возвращает управление, пока сервер не остановится. Это нормально для сервера, выполняющего единственную задачу. Приложение, которое также захватывает кадры или выполняет другие корутины, вместо этого использует start_server() внутри asyncio.run(), чтобы HTTP-сервер мог разделять цикл со всем остальным.

Приложение отвечает на один URL.