O que uma requisição crua de fato contém
Uma requisição HTTP/1.1 é uma linha de requisição, um bloco de cabeçalhos, uma linha em branco e um corpo opcional. A sintaxe está na RFC 9112 e o significado das partes na RFC 9110, e o formato quase não mudou desde os anos 1990:
POST /v1/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
{"name":"Alice"}
Existem três formas de alvo de requisição, e vale distingui-las, porque elas decidem o que um tradutor consegue fazer com a mensagem. A forma de origem é a comum, só o caminho. A forma absoluta carrega a URL inteira e é o que um cliente envia a um proxy. A forma asterisco é um * sozinho, usada com OPTIONS para perguntar sobre o servidor, e não sobre um recurso.
A URL não está na mensagem
Esta é a primeira armadilha. GET /users HTTP/1.1 não diz para onde enviar a requisição; a conexão já sabia. Reconstruir algo executável significa juntar o alvo ao cabeçalho Host, que é exatamente a razão de ele ter se tornado obrigatório no HTTP/1.1 - foi o que permitiu muitos sites dividirem um endereço, e é o que torna uma requisição capturada reconstruível.
Se o Host estiver ausente numa requisição em forma de origem, a URL não pode ser recuperada só da mensagem. A ferramenta deve dizer isso, em vez de inventar um host.
Alguns cabeçalhos não podem ser copiados
A segunda armadilha é mais sutil, e produz defeitos que parecem problema de servidor.
O Host é definido por todo cliente a partir da URL que você passa. Copiá-lo como cabeçalho explícito te dá dois, e qual vence depende do cliente.
O Content-Length é calculado pelo cliente a partir do corpo que ele vai enviar. Copiar o valor capturado fixa um número que pode não ser mais verdadeiro - e um comprimento que discorda do corpo não é erro cosmético. É o ingrediente do contrabando de requisições, em que uma ponta e a outra discordam sobre onde uma mensagem termina e a seguinte começa.
O Connection descreve o comportamento daquele salto específico, e não o significado da requisição.
Ou seja: uma tradução fiel não é uma tradução literal. Reproduzir cada cabeçalho ao pé da letra produz algo menos parecido com a requisição original, e não mais.
O texto colado em geral é uma credencial
A terceira armadilha não tem nada a ver com sintaxe. Requisições capturadas de uma sessão em funcionamento carregam cabeçalhos Authorization, cabeçalhos Cookie, chaves de API em strings de consulta - e costumam ser capturadas justamente porque algo estava dando errado, que é o momento em que as pessoas as colam em chamados, conversas e rastreadores de defeito.
Um token no chamado é credencial válida até expirar, legível por todo mundo com acesso àquele chamado, e pesquisável muito depois. Trate qualquer requisição colada como segredo vivo: censure antes de compartilhar, e prefira ferramenta que analise localmente, em vez de enviar a mensagem a um serviço para ser formatada.
Corpos em pedaços, e saber a hora de parar
Se a mensagem carrega Transfer-Encoding: chunked, o corpo que você vê não é o corpo enviado: é uma sequência de pedaços prefixados por tamanho, terminada por um de tamanho zero. Decodificar isso é trabalho diferente de traduzir uma requisição, e uma ferramenta que faz isso pela metade - decodificando algumas codificações e outras não - é pior que uma que diz claramente que não fez.
A rotina prática
- Identifique a forma do alvo. Se for de origem, ache o
Host. - Descarte os cabeçalhos que o cliente vai definir sozinho.
- Note se o corpo é em pedaços ou tem comprimento declarado, e se o comprimento declarado é verdadeiro.
- Censure credenciais antes de a requisição sair da sua máquina de qualquer forma.
- Traduza, e então execute o resultado contra um alvo de teste, e não contra produção, porque uma requisição repetida repete o que a original fez - inclusive a escrita.