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