Pular para o conteúdo

Interfaces locais

O Agent oferece duas interfaces locais para o seu servidor pedir uma operação: uma por HTTP em loopback (interface A) e uma por spool de diretório (interface C). As duas fazem exatamente as mesmas operações — a diferença é o meio de transporte, e você escolhe pela capacidade da sua stack. Um servidor que tem um cliente HTTP usa a interface A; um servidor que só consegue escrever arquivos usa a interface C.

Nenhuma das duas expõe criptografia ao seu servidor: você manda JSON simples e recebe JSON simples. O trabalho de assinar, anexar jti e exp, enviar e repetir com backoff acontece dentro do Agent. É esse o ponto do componente.

A interface A é um servidor HTTP que escuta em 127.0.0.1:8899 (configurável). O seu servidor faz um POST com o nome da operação e a entrada, e recebe o resultado. Serve qualquer stack com cliente HTTP — Node, PHP, Go, C++.

O corpo é um JSON com três campos: operation (o nome da operação, obrigatório), input (os campos da operação) e um id opcional que serve de chave de idempotência.

Terminal window
curl -s http://127.0.0.1:8899/call \
-H 'content-type: application/json' \
-d '{
"operation": "market.eligibility",
"input": { "characterRef": "c1" },
"id": "req-8f3a"
}'

A resposta é sempre um JSON com o campo ok. Em sucesso, o resultado da plataforma vem dentro de data; o id, quando você o enviou, é ecoado de volta:

{ "ok": true, "id": "req-8f3a", "data": { "eligible": true } }

Quando a plataforma recusa a operação (uma regra de negócio, por exemplo), a resposta traz ok: false, a message da plataforma e o status HTTP original. A message já vem em pt-BR e pronta para exibir — o Agent a repassa exatamente como veio, e o seu jogo pode mostrá-la ao jogador sem reescrever nada:

{ "ok": false, "id": "req-8f3a", "message": "O personagem não está vinculado a uma conta.", "status": 409 }

O campo id é o que torna o retry seguro. O Lua do servidor reenvia quando não tem certeza de que a primeira tentativa chegou — e frequentemente ele não tem como saber. Sem uma chave de idempotência, esse “não sei se deu certo” viraria uma segunda chamada de verdade à plataforma.

Quando você envia um id, o Agent guarda o resultado daquela chamada por 10 minutos. Uma segunda chamada com o mesmo id dentro dessa janela devolve o resultado guardado, sem tocar a plataforma de novo. Isso vale inclusive para recusas de negócio (4xx da plataforma): repetir daria a mesma recusa, então o Agent a guarda e evita o reenvio inútil a cada retry do Lua.

Dois endpoints de leitura completam a interface. GET /operations lista todas as operações disponíveis, cada uma com um resumo de uma linha:

Terminal window
curl -s http://127.0.0.1:8899/operations

GET /health confirma que o Agent está vivo e diz a versão:

{ "ok": true, "agent": "lootfy-agent/0.1.0" }

A interface C é para stacks que não têm cliente HTTP — o caso que a motivou é o Lua do TFS, onde db.* é a única saída de rede e uma fila em banco exigiria o Agent lendo o banco do parceiro, o que a premissa fundamental proíbe. Arquivo resolve os dois problemas de uma vez. Ela é ligada apontando LOOTFY_SPOOL_DIR para um diretório.

O diretório tem dois subdiretórios: o seu servidor escreve o pedido em outbox/ e a resposta em inbox/.

<spool>/outbox/<id>.json ← o seu servidor escreve, o Agent consome
<spool>/inbox/<id>.json ← o Agent escreve, o seu servidor consome

O pedido é um JSON com um id de correlação, a action (o nome da operação, igual ao operation da interface A) e os campos da operação no mesmo nível:

{ "id": "req-8f3a", "action": "market.eligibility", "characterRef": "c1" }

A resposta é escrita em inbox/<id>.json, com o mesmo id, um campo ok, e — em sucesso — os dados da plataforma planos, no nível de topo. Uma recusa traz ok: false, a message em pt-BR e o status:

{ "ok": true, "id": "req-8f3a", "eligible": true }

O worker varre o outbox/ a cada 200 ms e processa até 8 pedidos em voo ao mesmo tempo. Ao pegar um arquivo, ele o renomeia para reivindicá-lo — o rename é atômico no mesmo sistema de arquivos, então dois ticks nunca processam o mesmo arquivo duas vezes; quem perde a corrida simplesmente desiste. A resposta é escrita primeiro em um arquivo temporário e só então renomeada para o nome final, para que o seu servidor nunca leia um JSON pela metade no meio da escrita.

Interface A — HTTP

Escolha quando a sua stack tem um cliente HTTP (Node, PHP, Go, C++, um servidor que consegue fazer requisições). É a mais direta: um POST e uma resposta síncrona. Exige que o servidor e o Agent compartilhem o loopback da host.

Interface C — spool

Escolha quando a sua stack só consegue mexer em arquivo, ou quando fazer uma chamada de rede síncrona travaria o servidor. É a rota recomendada para TFS: io.popen seria síncrono na thread do dispatcher, e congelar o mundo a cada anúncio para esperar uma chamada de rede é inaceitável.