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-соединение, записывает строку запроса и заголовки и ждёт. Камера считывает байты из сокета, разбирает их в объект microdot.Request, сопоставляет путь и метод с таблицей маршрутизации, ожидает корутину обработчика, сериализует всё, что она вернула, записывает строку статуса, заголовки и тело обратно в сокет, а затем закрывает соединение (по умолчанию HTTP/1.0) или переиспользует его (HTTP/1.1 с Connection: keep-alive). Весь обмен занимает примерно столько же времени, сколько сетевой круговой путь плюс то, что сделал обработчик.
10.1.3. Замечание о блокировке¶
run() является блокирующим – он никогда не возвращает управление, пока сервер не остановится. Это нормально для сервера, выполняющего единственную задачу. Приложение, которое также захватывает кадры или выполняет другие корутины, вместо этого использует start_server() внутри asyncio.run(), чтобы HTTP-сервер мог разделять цикл со всем остальным.
Приложение отвечает на один URL.