10.1. Tu primer endpoint

Antes de que la cámara pueda hacer algo interesante, el resto de la red tiene que poder alcanzarla. Lo más barato que demuestra que el servidor está vivo es un endpoint HTTP de una sola ruta que devuelve algo de 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)

Ejecútalo en el IDE. Desde cualquier otra máquina de la LAN, abre http://<cam-ip>/status. El navegador muestra:

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

Los contadores son marcadores de posición – nada los está tocando todavía – pero la petición cruzó la red, la cámara la enrutó, ejecutó un manejador y devolvió JSON.

10.1.1. Qué hace cada línea

Una instancia de microdot.Microdot por script. La instancia posee la tabla de enrutamiento, los manejadores de errores y el ciclo de vida (iniciar, servir, detener). Las aplicaciones grandes se dividen en varios módulos de Python pero siguen compartiendo un único objeto app.

@app.get('/status') es el decorador de ruta. Aquí solo usamos microdot.Microdot.get(); post(), put() y delete() aparecen en páginas posteriores cuando la cámara empieza a aceptar escrituras.

Cada manejador de ruta es una corrutina de asyncio y recibe la petición como su primer argumento. El manejador no tiene por qué usar request – este la ignora – pero el parámetro siempre está ahí para que la firma sea consistente.

Devolver un dict es la forma más corta de enviar JSON. Microdot serializa el dict a JSON automáticamente y establece Content-Type: application/json en la respuesta. Devolver una cadena envía text/plain. Devolver una microdot.Response explícitamente es la forma larga – necesaria cuando el cuerpo es binario o cuando la respuesta quiere cabeceras personalizadas.

app.run(host='0.0.0.0', port=80) inicia el servidor. 0.0.0.0 significa escuchar en todas las interfaces que tenga la cámara – tanto la ethernet cableada como la STA de wifi, si ambas están activas. El puerto 80 es el predeterminado de HTTP, por lo que los navegadores no necesitan escribir un número de puerto.

10.1.2. Una petición, de principio a fin

El teléfono abre una conexión TCP con la cámara, envía una petición HTTP, la cámara la analiza, la enruta, ejecuta el manejador y luego escribe una respuesta de vuelta.

El teléfono abre una conexión TCP, escribe la línea de petición y las cabeceras, y espera. La cámara lee los bytes del socket, los analiza para formar un objeto microdot.Request, coteja la ruta y el método con la tabla de enrutamiento, espera a la corrutina del manejador, serializa lo que esta haya devuelto, escribe una línea de estado, cabeceras y cuerpo de vuelta por el socket, y luego cierra la conexión (predeterminado de HTTP/1.0) o la recicla (HTTP/1.1 con Connection: keep-alive). Todo el intercambio dura aproximadamente lo que tarda el viaje de ida y vuelta por la red más lo que haya hecho el manejador.

10.1.3. Una nota sobre el bloqueo

run() es bloqueante – nunca retorna hasta que el servidor se detiene. Eso está bien para un servidor de propósito único. Una aplicación que además captura fotogramas o ejecuta otras corrutinas usa start_server() dentro de un asyncio.run() en su lugar, para que el servidor HTTP pueda compartir el bucle con todo lo demás.

La aplicación responde a una URL.