10.1. Seu primeiro endpoint

Antes que a câmera possa fazer qualquer coisa interessante, o restante da rede precisa ser capaz de alcançá-la. A coisa mais barata que comprova que o servidor está vivo é um endpoint HTTP de uma única rota que retorna algum 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-o na IDE. De qualquer outra máquina na LAN, abra http://<cam-ip>/status. O navegador exibe:

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

Os contadores são marcadores de posição – nada está mexendo neles ainda – mas a requisição atravessou a rede, a câmera a roteou, executou um handler e devolveu o JSON.

10.1.1. O que cada linha faz

Uma instância de microdot.Microdot por script. A instância é dona da tabela de roteamento, dos handlers de erro e do ciclo de vida (iniciar, atender, parar). Aplicações grandes se dividem em vários módulos Python, mas ainda compartilham um único objeto app.

@app.get('/status') é o decorador de rota. Aqui usamos apenas microdot.Microdot.get(); post(), put() e delete() aparecem em páginas posteriores, quando a câmera começa a aceitar escritas.

Todo handler de rota é uma corrotina asyncio e recebe a requisição como seu primeiro argumento. O handler não precisa usar request – este aqui o ignora – mas o parâmetro está sempre presente, de modo que a assinatura seja consistente.

Retornar 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. Retornar uma string envia text/plain. Retornar um microdot.Response explicitamente é a forma longa – necessária quando o corpo é binário ou quando a resposta quer 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âmera possui – tanto a ethernet cabeada quanto a STA wifi, se ambas estiverem ativas. A porta 80 é o padrão do HTTP, então os navegadores não precisam digitar um número de porta.

10.1.2. Uma requisição, de ponta a ponta

O telefone abre uma conexão TCP com a câmera, envia uma requisição HTTP, a câmera faz o parsing, roteia, executa o handler e então escreve uma resposta de volta.

O telefone abre uma conexão TCP, escreve a linha de requisição e os cabeçalhos, e aguarda. A câmera lê os bytes do socket, faz o parsing deles em um objeto microdot.Request, compara o caminho e o método com a tabela de roteamento, aguarda a corrotina do handler, serializa o que quer que ele tenha retornado, escreve uma linha de status, cabeçalhos e corpo de volta pelo socket, e então fecha a conexão (padrão do HTTP/1.0) ou a recicla (HTTP/1.1 com Connection: keep-alive). Toda a troca leva aproximadamente o tempo do round-trip da rede mais o que quer que o handler tenha feito.

10.1.3. Uma observação sobre bloqueio

run() é bloqueante – nunca retorna até que o servidor pare. Isso é aceitável para um servidor de propósito único. Uma aplicação que também captura quadros ou executa outras corrotinas usa start_server() dentro de um asyncio.run(), para que o servidor HTTP possa compartilhar o loop com todo o resto.

A aplicação responde a uma URL.