Redes, sistemas e decisões de engenharia
Neste laboratório haverá tráfego HTTP real somente em 127.0.0.1. Node.js 24 fornece servidor, fetch e asserções sem instalar pacotes. Dois tokens fictícios identificam Ana da loja A e Bia da loja B; não representam login de produção. O estoque contém três unidades. A entrega é uma sequência de requisições, falhas e provas de que apenas uma reserva foi aplicada.
Salve contratos-http.cjs numa pasta de estudo. Antes de executar, desenhe quais estados ficam no cliente e no servidor. A simulação não usa armazenamento durável; interromper o cliente é diferente de reiniciar o processo do servidor. Essa distinção deve aparecer na entrega. Você também redigirá critérios de interface acessível e uma decisão ética sobre quais dados registrar, sem afirmar que uma API isolada implementa a experiência completa.
RedesAo terminar esta aula
- Observe estado além de status HTTP.
- Vincule replay a identidade e conteúdo.
- Declare persistência, segurança e acessibilidade que ainda precisam de implementação.
Antes de continuar: Leitura: Redes, sistemas e decisões de engenharia
Leia o contrato antes da primeira requisição
FundamentosO servidor só reconhece /lojas/A/produtos/I2. Uma identidade ausente recebe 401; a identidade da loja B recebe 404 sem detalhes do recurso privado. GET devolve sku, saldo e revisão; POST recebe quantidade e exige chave de idempotência. Leia a ordem dos predicados: identidade e tenant são conferidos antes de cache e recibos. Se o programa consultasse o ledger primeiro, uma chave conhecida poderia revelar trabalho de outra pessoa.
A rotina responder produz JSON e uma política de cache privado. Compare a primeira resposta com a requisição que inclui If-None-Match. A segunda deve receber 304, que não contém uma nova representação: o cliente conserva o corpo da primeira. Acrescente o mesmo ETag no pedido de Bia e confirme 404. Esse teste identifica um defeito plausível em sistemas que retornam "não mudou" antes de verificar a autorização, revelando a existência ou versão de um recurso privado.
Interrompa o acompanhamento depois do efeito
FundamentosO cabeçalho x-simular-atraso existe somente para provocar a falha do laboratório. O servidor altera saldo, incrementa revisão e grava recibo, então espera antes de responder. O cliente observa o contador interno do fixture para interromper exatamente depois do efeito; em um serviço real ele não teria esse acesso. O mecanismo evita depender da sorte de um timeout curto ocorrer antes ou depois da alteração. A asserção exige que a espera abortada realmente rejeite.
Depois de esperar o handler terminar, o cliente repete OP1 com quantidade um. O servidor encontra o binding da operação e retorna REC-OP1, mantendo um efeito. Compare os significados dos status: 201 representa a criação nominal do recibo; 200 representa a recuperação do existente neste contrato. Não conte respostas 200 como novas reservas. Capture o saldo final e a quantidade de efeitos; só o status ou o log do cliente não provam que a repetição foi reconhecida.
Teste conteúdo, versão e identidade separadamente
AvaliaçãoReutilize OP1 com quantidade dois. O teste exige 409, pois o conteúdo não corresponde ao recibo autorizado. Use então OP2 com a revisão inicial: o teste exige 412 e mantém saldo dois. Esse segundo pedido é uma intenção nova com pré-condição antiga. Consultar novamente com o ETag antigo deve receber 200 e o novo saldo, demonstrando que a versão mudou. A diferença entre replay e operação nova está codificada, não inferida do texto de uma mensagem.
Envie quantidade null e exija 400. JSON.parse aceitou o corpo, mas o domínio recusou o valor. Para ampliar o teste, tente zero, número fracionário e string. Acrescente também um método diferente e observe 405 com Allow. Mantenha uma tabela com caso, pré-condição, status, saldo e recibo. A rubrica pede que erros deixem o estado intacto; tratar toda exceção como sucesso torna essa evidência impossível. O exemplo usa funções e campos padronizados, mas a política comercial é específica desta aplicação.
Registre limites e critérios que pessoas possam revisar
FundamentosEscreva uma decisão de engenharia: o ledger Map é suficiente para demonstrar replay durante uma execução local, mas perde recibos no reinício e não coordena múltiplos servidores. Uma versão durável pode reaproveitar o raciocínio transacional da semana anterior. Não copie os tokens fictícios para um ambiente compartilhado; eles não possuem expiração, assinatura nem proteção de segredo. O relatório deve identificar essas limitações como tarefas concretas, sem apresentar o protótipo como sistema pronto.
↗ ACM Code of Ethics and Professional Conduct↗ WAI: Introduction to Web Accessibility
Defina uma tela de reserva com quantidade rotulada, confirmação textual e mensagem recuperável quando o resultado é desconhecido. A pessoa deve conseguir consultar a operação existente, em vez de ser incentivada a tentar novamente como compra nova. Planeje navegação por teclado e anúncio de erro, evitando usar só cor. Registre somente identificadores necessários para diagnóstico, não o token nem o conteúdo pessoal indiscriminado. A obrigação de evitar dano e respeitar privacidade exige olhar quem pode ser afetado pela duplicação e pelo vazamento, além de quem encomendou a função.
↗ ACM Code of Ethics and Professional Conduct↗ WAI: Introduction to Web Accessibility
Exercício aplicado
O cliente consulta saldo três, envia uma reserva e perde a conexão depois de o servidor alterar o estoque. Um retry sem identidade pode repetir o efeito; um cache compartilhado pode mostrar dados de outra loja. Defina contratos que permitam recuperar o recibo sem confundir falha de acompanhamento com falha do trabalho.
- Desenhe cliente, servidor, armazenamento e fronteiras de identidade antes de executar.
- Use ETag para validar leitura e If-Match para proteger uma nova alteração contra revisão antiga.
- Interrompa o cliente depois do efeito e repita a operação com mesma chave e conteúdo.
- Teste outra identidade, conteúdo alterado e revisão antiga; declare persistência e acessibilidade ainda não implementadas.
Abrir resolução comentada
O cliente não recebeu a primeira resposta, mas o servidor já registrou recibo. A repetição autorizada com a mesma chave e binding devolve o recibo existente; quantidade diferente gera 409. A revisão antiga bloqueia uma operação nova, enquanto o replay reconhecido não representa uma nova alteração.
A autorização ocorre antes de qualquer consulta ao ledger ou de comparar ETag. GET usa validação condicional e cache privado; isso não concede acesso a outro usuário. O servidor real roda somente em loopback e o ledger fica em memória: reinício perde essa proteção. A operação é indivisível apenas dentro deste handler síncrono de um processo; produção exige armazenamento transacional e mecanismo de concorrência apropriados.
const http=require('node:http'),assert=require('node:assert/strict');
const esperar=ms=>new Promise(r=>setTimeout(r,ms));
const identidades=new Map([['ana',{ator:'ana',tenant:'A'}],['bia',{ator:'bia',tenant:'B'}]]);
const estoque={tenant:'A',sku:'I2',saldo:3,revisao:1},recibos=new Map();let efeitos=0;
const etag=()=> '"rev-'+estoque.revisao+'"';
function responder(res,status,body,headers={}){if(res.destroyed)return;
res.writeHead(status,{'content-type':'application/json','cache-control':'private, max-age=0, must-revalidate',...headers});
res.end(status===304?'':JSON.stringify(body));}
const server=http.createServer(async(req,res)=>{try{
const identidade=identidades.get((req.headers.authorization||'').replace(/^Bearer /,''));
if(!identidade)return responder(res,401,{erro:'identidade necessaria'},{'www-authenticate':'Bearer'});
if(req.url!=='/lojas/A/produtos/I2'||identidade.tenant!==estoque.tenant)return responder(res,404,{erro:'recurso nao acessivel'});
if(req.method==='GET'){
if(req.headers['if-none-match']===etag())return responder(res,304,null,{etag:etag()});
return responder(res,200,{sku:estoque.sku,saldo:estoque.saldo,revisao:estoque.revisao},{etag:etag()});}
if(req.method!=='POST')return responder(res,405,{erro:'metodo nao permitido'},{allow:'GET, POST'});
let raw='';for await(const c of req){raw+=c;if(raw.length>4096)return responder(res,413,{erro:'corpo grande'});}
const p=JSON.parse(raw),chave=req.headers['idempotency-key'];
if(!Number.isSafeInteger(p.quantidade)||p.quantidade<=0||typeof chave!=='string'||!chave)return responder(res,400,{erro:'entrada invalida'});
const binding=JSON.stringify([identidade.tenant,identidade.ator,req.url,p.quantidade]);
const key=JSON.stringify([identidade.tenant,identidade.ator,chave]),anterior=recibos.get(key);
if(anterior){if(anterior.binding!==binding)return responder(res,409,{erro:'chave com conteudo diferente'});return responder(res,200,anterior.recibo);}
if(!req.headers['if-match'])return responder(res,428,{erro:'revisao necessaria'});
if(req.headers['if-match']!==etag())return responder(res,412,{erro:'revisao mudou'});
if(p.quantidade>estoque.saldo)return responder(res,409,{erro:'saldo insuficiente'});
// Sem await entre checagem e mutacao neste processo unico.
estoque.saldo-=p.quantidade;estoque.revisao++;efeitos++;
const recibo={id:'REC-'+chave,quantidade:p.quantidade,revisao:estoque.revisao};recibos.set(key,{binding,recibo});
if(req.headers['x-simular-atraso'])await esperar(80);
return responder(res,201,recibo);
}catch(e){return responder(res,400,{erro:'corpo invalido'});}});
(async()=>{await new Promise(r=>server.listen(0,'127.0.0.1',r));
const url='http://127.0.0.1:'+server.address().port+'/lojas/A/produtos/I2';
const headers={authorization:'Bearer ana','content-type':'application/json'};
try{
const r=await fetch(url,{headers}),primeiro=await r.json(),versao=r.headers.get('etag');assert.equal(primeiro.saldo,3);
assert(r.headers.get('cache-control').startsWith('private'));
const validacao=await fetch(url,{headers:{...headers,'if-none-match':versao}});assert.equal(validacao.status,304);
assert.equal((await fetch(url,{headers:{...headers,authorization:'Bearer bia','if-none-match':versao}})).status,404);
assert.equal((await fetch(url)).status,401);
const abort=new AbortController();
const tentativa=fetch(url,{method:'POST',headers:{...headers,'if-match':versao,'idempotency-key':'OP1','x-simular-atraso':'1'},body:JSON.stringify({quantidade:1}),signal:abort.signal});
// Espere o efeito, mas interrompa antes da resposta retardada.
const limite=Date.now()+5000;while(efeitos===0&&Date.now()<limite)await esperar(1);assert.equal(efeitos,1);abort.abort();await assert.rejects(()=>tentativa);await esperar(90);
const replay=await fetch(url,{method:'POST',headers:{...headers,'if-match':versao,'idempotency-key':'OP1'},body:JSON.stringify({quantidade:1})});
assert.equal(replay.status,200);assert.equal((await replay.json()).id,'REC-OP1');assert.equal(efeitos,1);
const mudou=await fetch(url,{method:'POST',headers:{...headers,'if-match':versao,'idempotency-key':'OP1'},body:JSON.stringify({quantidade:2})});assert.equal(mudou.status,409);
const antiga=await fetch(url,{method:'POST',headers:{...headers,'if-match':versao,'idempotency-key':'OP2'},body:JSON.stringify({quantidade:1})});assert.equal(antiga.status,412);
const novo=await fetch(url,{headers:{...headers,'if-none-match':versao}});assert.equal(novo.status,200);assert.equal((await novo.json()).saldo,2);
assert.equal((await fetch(url,{method:'POST',headers:{...headers,'idempotency-key':'INVALIDO'},body:JSON.stringify({quantidade:null})})).status,400);
assert.equal(efeitos,1);console.log('HTTP real: ETag, identidade, timeout, replay vinculado e revisao verificados');
}finally{server.closeAllConnections();await new Promise(r=>server.close(r));}
})().catch(e=>{console.error(e);process.exitCode=1;});Como conferir seu resultado
- Outra identidade não recebe representação nem confirmação de ETag.
- Abort do cliente não é descrito como rollback do servidor.
- Replay preserva um único efeito e conteúdo alterado é recusado.
- Nova operação exige revisão atual; cache não substitui autorização.
- README identifica limites do Map e critérios de interface acessível.
Teste sua compreensão
Responda com suas palavras antes de abrir o comentário. Saber explicar uma decisão é parte do domínio.
1. Por que esperar o contador de efeito antes do abort no fixture?
Para tornar determinístico o ponto de falha estudado.
O teste deve demonstrar perda de acompanhamento após a alteração, não um pedido que nem chegou.
2. Qual experimento ainda falta para provar continuidade após reinício?
Um ledger persistente e recuperação em outro processo.
Map perde o estado; o teste atual permanece no mesmo servidor.
3. Passar testes HTTP comprova acessibilidade?
Não.
Teclado, foco, rótulos e anúncios precisam de implementação e avaliação da interface.
Seu progresso fica salvo neste navegador. Concluir a leitura não substitui demonstrar o domínio nos exercícios.
Referências e aprofundamento
Documentação oficial e trabalhos originais. As referências registram o escopo e as limitações para você conferir o que sustentam.
- RFC9110: HTTP Semantics
IETF / RFC Editor • consulta: 2026-10-06
FundamentosMétodos, status, identidade de representação e requisições condicionais.
Limites: Política de idempotency-key do exemplo e contrato da aplicação, não garantia universal de POST.
- RFC9111: HTTP Caching
IETF / RFC Editor • consulta: 2026-10-06
FundamentosCache-Control, validação e reutilização de representações.
Limites: Cache não autentica nem autoriza acesso ao recurso.
- WAI: Introduction to Web Accessibility
W3C WAI • consulta: 2026-10-06
FundamentosAcessibilidade como propriedade do projeto e experiência das pessoas.
Limites: Introdução não e checklist completo WCAG; API JSON não demonstra acessibilidade da interface.
- ACM Code of Ethics and Professional Conduct
ACM • consulta: 2026-10-06
FundamentosEvitar dano, respeitar privacidade e considerar pessoas afetadas.
Limites: Código profissional não substitui análise legal ou avaliação específica de impacto.