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, 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.