10.1. O seu primeiro endpoint

Antes de a câmara poder fazer algo interessante, o resto da rede tem de conseguir alcançá-la. A coisa mais simples que prova que o servidor está ativo é um endpoint HTTP de uma única rota que devolve 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)

Execute no IDE. A partir de qualquer outra máquina na LAN, abra http://<cam-ip>/status. O browser apresenta:

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

Os contadores são marcadores de posição – ainda nada os está a modificar – mas o pedido atravessou a rede, a câmara encaminhou-o, executou um handler e devolveu JSON.

10.1.1. O que faz cada linha

Uma instância de microdot.Microdot por script. A instância é proprietária da tabela de rotas, dos handlers de erro e do ciclo de vida (iniciar, servir, parar). Aplicações maiores dividem-se em vários módulos Python, mas partilham sempre um único objeto app.

@app.get('/status') é o decorador de rota. Aqui estamos a utilizar apenas microdot.Microdot.get(); post(), put() e delete() surgem nas páginas seguintes, quando a câmara começa a aceitar escritas.

Cada handler de rota é uma corrotina asyncio e recebe o pedido como primeiro argumento. O handler não tem de usar request – este ignora-o – mas o parâmetro está sempre presente para que a assinatura seja consistente.

Devolver um dict é a forma mais curta de enviar JSON. O Microdot serializa o dict para JSON automaticamente e define Content-Type: application/json na resposta. Devolver uma string envia text/plain. Devolver explicitamente um microdot.Response é a forma longa – necessária quando o corpo é binário ou quando a resposta requer cabeçalhos personalizados.

app.run(host='0.0.0.0', port=80) inicia o servidor. 0.0.0.0 significa escutar em todas as interfaces que a câmara tiver – tanto a ethernet com fios como o Wi-Fi STA, se ambas estiverem ativas. A porta 80 é o valor predefinido do HTTP, pelo que os browsers não precisam de indicar um número de porta.

10.1.2. Um pedido, do início ao fim

The phone opens a TCP connection to the cam, sends an HTTP request, the cam parses, routes, runs the handler, then writes a response back.

O telemóvel abre uma ligação TCP, escreve a linha de pedido e os cabeçalhos, e aguarda. A câmara lê os bytes do socket, analisa-os num objeto microdot.Request, faz a correspondência do caminho e método com a tabela de rotas, aguarda a corrotina do handler, serializa o que esta devolveu, escreve uma linha de estado, cabeçalhos e corpo de volta no socket, e depois fecha a ligação (predefinição HTTP/1.0) ou reutiliza-a (HTTP/1.1 com Connection: keep-alive). A troca completa demora aproximadamente o tempo de ida e volta na rede mais o que o handler executou.

10.1.3. Uma nota sobre bloqueio

run() é bloqueante – nunca retorna enquanto o servidor não parar. Isto é adequado para um servidor de propósito único. Uma aplicação que também capture fotogramas ou execute outras corrotinas utiliza start_server() dentro de um asyncio.run(), para que o servidor HTTP possa partilhar o ciclo com tudo o resto.

A aplicação responde a um único URL.