A internet é uma parte intrínseca da nossa vida diária, e por trás de cada clique, cada página carregada e cada mensagem enviada, existe um protocolo fundamental: o HTTP. Este artigo tem como objetivo desmistificar o HTTP, explicando seus conceitos de forma clara e passo a passo, ideal para quem está começando no mundo do desenvolvimento web e Java.
1. Introdução ao HTTP: A Base da Web
O HTTP, ou Hypertext Transfer Protocol (Protocolo de Transferência de Hipertexto), é a espinha dorsal de qualquer troca de dados na World Wide Web. Ele funciona como a "linguagem" padronizada que navegadores web (clientes) e servidores web utilizam para interagir e trocar informações.
Sua função principal é buscar recursos como documentos HTML, imagens, vídeos, ou enviar dados para servidores, como os resultados de formulários web. A natureza extensível do HTTP permite que ele seja usado para muito mais do que apenas documentos de hipertexto, incluindo a atualização de partes de páginas web sob demanda, o que é essencial para as experiências interativas modernas. Sem o HTTP, a World Wide Web como a conhecemos simplesmente não existiria, pois ele é o "carteiro" digital que garante a entrega do conteúdo que os usuários veem e interagem online.
O HTTP reside na camada de aplicação do modelo de rede, o que significa que ele opera no nível mais alto, lidando diretamente com a interação entre softwares, como navegadores e servidores web. Embora o HTTP opere nesta camada superior, ele é construído sobre um protocolo de transporte confiável. Historicamente, o Transmission Control Protocol (TCP) tem sido a escolha padrão para a maioria das versões do HTTP. O TCP é crucial porque garante que os dados cheguem ao destino na ordem correta e sem perdas, abstraindo essa complexidade do HTTP.
A forma como o HTTP se integra com as camadas de rede subjacentes é um aspecto fundamental do seu design. As camadas de transporte, rede e física são "escondidas" e transparentes no nível HTTP, embora possam impactar o desempenho geral da comunicação. Essa separação de preocupações entre as camadas do modelo de rede permite que o HTTP se concentre exclusivamente na lógica da aplicação, como buscar recursos e enviar dados, sem precisar gerenciar os detalhes complexos de como os pacotes de dados são roteados, entregues ou retransmitidos em caso de perda. Essa abstração simplifica enormemente o desenvolvimento web, pois o desenvolvedor de aplicações não precisa se preocupar diretamente com a confiabilidade da rede.
Além disso, essa flexibilidade no design permite que o HTTP evolua, como na transição para o HTTP/3, que utiliza QUIC sobre UDP, sem que a interface de aplicação mude drasticamente. Essa adaptabilidade demonstra a robustez e a resiliência do design em camadas da internet, tornando o HTTP um protocolo altamente durável e capaz de se adaptar às crescentes demandas da web. Recursos HTTP são identificados e localizados na rede usando Uniform Resource Locators (URLs), que empregam os esquemas de Uniform Resource Identifiers (URIs) http e https. Em cenários de rede local, versões como HTTPMU e HTTPU (baseadas em UDP) também podem ser utilizadas para protocolos como UPnP e SSDP.
2. O Modelo Cliente-Servidor no HTTP
O HTTP opera estritamente no modelo cliente-servidor, onde a comunicação é sempre iniciada pelo cliente, que é o "solicitante", e o servidor, o "fornecedor de recursos", responde a essa solicitação. O cliente mais comum é um navegador web (como Chrome, Firefox ou Safari), mas pode ser qualquer software que inicie uma requisição, incluindo aplicativos móveis, programas de linha de comando como curl
, ou "robôs" (web crawlers) que indexam a web para motores de busca. O cliente é responsável por traduzir as ações do usuário, como clicar em um link ou digitar um URL, em requisições HTTP.
Do outro lado da comunicação, o servidor web é o computador ou software que "serve" os documentos ou recursos solicitados pelo cliente. É importante notar que um único servidor físico pode hospedar múltiplos servidores virtuais, permitindo que diferentes domínios compartilhem o mesmo endereço IP, uma capacidade habilitada pelo cabeçalho Host no HTTP/1.1
O fluxo básico da comunicação entre cliente e servidor segue uma sequência padronizada: primeiro, o cliente estabelece uma conexão TCP com o servidor.Em seguida, o cliente envia uma mensagem de requisição HTTP. O servidor processa essa requisição e, por sua vez, envia uma mensagem de resposta HTTP de volta ao cliente. Após a troca de mensagens, a conexão pode ser fechada ou reutilizada para requisições futuras, dependendo da versão do HTTP e da configuração específica.
Entre o cliente e o servidor, podem existir entidades intermediárias chamadas proxies. Operando na camada de aplicação, os proxies podem realizar diversas operações que otimizam ou controlam o tráfego de rede. As funções comuns dos proxies incluem o cache, onde armazenam cópias de respostas para requisições futuras, acelerando a entrega de conteúdo e reduzindo a carga no servidor de origem.
Eles também podem atuar na filtragem, implementando controles de segurança como antivírus ou filtros de conteúdo, e no balanceamento de carga, distribuindo requisições entre múltiplos servidores para otimizar o desempenho e a disponibilidade. Além disso, proxies podem gerenciar a autenticação para recursos protegidos e facilitar o tunelamento, permitindo que requisições HTTP atravessem barreiras de rede, como firewalls ou intranets, e ocultem o endereço IP real do cliente. O método CONNECT é especificamente usado para configurar um túnel HTTP. Outras funções incluem o registro (logging) de requisições e respostas para fins de auditoria ou análise.
É importante distinguir entre cabeçalhos hop-by-hop e cabeçalhos end-to-end no contexto de proxies.
Cabeçalhos Hop-by-Hop:
São significativos apenas para uma única conexão de nível de transporte (entre dois nós adjacentes, como cliente e proxy, ou proxy e servidor). Eles não são armazenados por caches nem encaminhados por proxies para o próximo "salto" (hop) na cadeia. Exemplos incluem Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, TE, Trailer e Transfer-Encoding. Um proxy compatível deve processar esses cabeçalhos e removê-los antes de encaminhar a requisição.
Cabeçalhos End-to-End:
São transmitidos ao destinatário final da requisição ou resposta (o servidor para uma requisição, ou o cliente para uma resposta). Proxies intermediários devem retransmitir esses cabeçalhos sem modificação, e caches devem armazená-los. Todos os outros cabeçalhos definidos pelo HTTP/1.1 são, por padrão, cabeçalhos end-to-end.
A ascensão da criptografia na web, por meio do HTTPS, alterou significativamente o papel dos proxies intermediários. Enquanto proxies tradicionais podiam inspecionar o conteúdo HTTP para realizar funções como caching e filtragem, o HTTPS criptografa toda a comunicação entre cliente e servidor usando TLS/SSL. Com o HTTPS, os proxies na rota geralmente só podem "tunelar" as respostas, pois o conteúdo está criptografado e eles não podem inspecioná-lo ou cacheá-lo diretamente, a menos que realizem uma descriptografia complexa, o que requer a instalação de certificados de Autoridade Certificadora (CA) organizacionais.
Essa transição massiva para HTTPS, impulsionada por questões de segurança e privacidade, mitigou muitos dos benefícios de "camada de aplicação" dos proxies transparentes. Isso levou ao surgimento e à popularização de Content Delivery Networks (CDNs) e caches gerenciados que operam mais próximos do servidor de origem ou do cliente (como Service Workers no navegador), para manter os benefícios de desempenho que os proxies intermediários perderam com a criptografia. A segurança, neste caso, tem precedência sobre a otimização de rede via proxies não gerenciados, exigindo novas abordagens para o gerenciamento de cache e tráfego na web moderna.
3. A Estrutura das Mensagens HTTP
A comunicação fundamental no HTTP ocorre através da troca de dois tipos de mensagens: requisições e respostas. As requisições são enviadas pelo cliente para iniciar uma ação no servidor, enquanto as respostas são enviadas pelo servidor em réplica a uma requisição. É importante notar que, na prática diária, desenvolvedores raramente constroem essas mensagens manualmente. Em vez disso, eles utilizam APIs e bibliotecas de alto nível que abstraem a complexidade da formatação e manipulação dessas mensagens, permitindo que se concentrem na lógica da aplicação.
Em HTTP/1.1 e versões anteriores, as mensagens são baseadas em texto e seguem uma estrutura relativamente fácil de entender. Essa estrutura consiste em quatro partes principais:
1. Linha de Início (Start-line):
A primeira linha da mensagem. Para requisições, é a "linha de requisição" (Request-Line), que especifica o método HTTP (a ação desejada), o recurso alvo e a versão do protocolo. Para respostas, é a "linha de status" (Status-Line), que indica a versão do protocolo, o código de status numérico (o resultado da requisição) e uma breve descrição textual legível por humanos.
2. Cabeçalhos HTTP (Headers):
Um conjunto opcional de linhas que contêm metadados sobre a mensagem. Cada cabeçalho é um par
- Nome: Valor, como Content-Type (indicando o tipo de mídia do corpo) ou User-Agent (identificando o cliente).
- Cabeçalhos fornecem contexto adicional para o servidor ou o cliente sobre como processar a mensagem.
3. Linha Vazia:
Uma linha em branco (CRLF) que serve como um separador, indicando o fim dos cabeçalhos e o início do corpo da mensagem, se houver.5 Sua presença é crucial para que o receptor saiba onde termina a parte de metadados e começa o conteúdo.
4. Corpo (Body):
Uma parte opcional que contém os dados reais associados à mensagem. Por exemplo, em uma requisição POST, o corpo pode conter dados de formulário enviados ao servidor; em uma resposta GET, pode conter o conteúdo de uma página HTML ou um arquivo de imagem. A presença de um corpo é determinada pela linha de início e pelos cabeçalhos HTTP.
A Linha de Início e os Cabeçalhos são frequentemente referidos como o "cabeçalho" (head) da mensagem, enquanto a parte que contém o conteúdo é conhecida como o "corpo" (body).
Detalhes da Linha de Início
A linha de requisição (Request-Line) em HTTP/1.x é composta por três partes: <método> <alvo-da-requisição> <protocolo>
<método>
:
O método HTTP (ou verbo) que define o significado da requisição e o resultado desejado (ex: GET para recuperar, POST para enviar dados).
<alvo-da-requisição>
:
Geralmente uma URL absoluta ou relativa, cujo formato depende do método HTTP e do contexto da requisição.
Existem várias formas para o alvo da requisição:
Forma de Origem:
A mais comum, onde o cliente envia apenas o caminho absoluto do recurso (ex: /en-US/docs/Web/HTTP/Guides/Messages). O servidor combina isso com o cabeçalho Host para formar a URL completa. Usada com GET, POST, HEAD e OPTIONS.
Forma Absoluta:
Uma URL completa, incluindo a autoridade (esquema, host, porta). Usada com GET ao se conectar a um proxy.
Forma de Autoridade:
A autoridade e a porta separadas por dois pontos (ex: developer.mozilla.org:443
). Usada apenas com o método CONNECT para configurar um túnel HTTP.
Forma de Asterisco:
Usada apenas com OPTIONS para representar o servidor como um todo (*), em vez de um recurso nomeado.
<protocolo>
:
A versão do HTTP, que dita a estrutura do restante da mensagem e indica a versão esperada para a resposta. Geralmente HTTP/1.1. Em HTTP/2 e posteriores, a versão do protocolo não é incluída nas mensagens, pois é inferida da configuração da conexão.
Linha de status (Status-Line)
Em respostas HTTP/1.x também tem três partes: <protocolo> <código-de-status> <texto-de-status>
.
<protocolo>
:
A versão do HTTP do restante da mensagem.
<código-de-status>
:
Um código de status numérico de três dígitos que indica o sucesso ou falha da requisição (ex: 200, 404, 302).
<texto-de-status>
:
Uma breve descrição textual informativa do código de status para compreensão humana.
Detalhes dos Cabeçalhos HTTP
Os cabeçalhos são metadados enviados com uma requisição ou resposta, seguindo a linha de início e precedendo o corpo. Em HTTP/1.x, cada cabeçalho é uma string que não diferencia maiúsculas de minúsculas, seguida por dois pontos e seu valor (ex: Content-Type: application/json
).
Existem diferentes categorias de cabeçalhos:
Cabeçalhos de Requisição: Fornecem contexto adicional ou lógica para o processamento do servidor (ex: User-Agent, Accept-Language).
Cabeçalhos de Resposta: Fornecem contexto adicional sobre a mensagem ou guiam o cliente em requisições subsequentes (ex: Server, Date, Cache-Control).
Cabeçalhos de Representação: Enviados se a mensagem tiver um corpo, descrevendo a forma original e a codificação dos dados da mensagem (ex: Content-Type, Content-Length).
Detalhes do Corpo da Mensagem
O corpo da requisição transporta informações para o servidor. Apenas requisições PATCH, POST e PUT geralmente contêm um corpo. O corpo pode conter pares chave=valor
(como em formulários HTML), dados JSON, ou dados multipartes (para upload de arquivos).
Para respostas, um corpo é incluído na maioria das respostas a um cliente. Em requisições GET bem-sucedidas, ele contém os dados solicitados. Para requisições que falharam, frequentemente explica o motivo da falha. No entanto, respostas com códigos de status como 1xx (informacionais)
, 204 No Content
e 304 Not Modified
, bem como respostas ao método HEAD, não devem incluir um corpo de mensagem.
Quando o tamanho do corpo da mensagem não é conhecido antecipadamente (por exemplo, ao gerar uma grande tabela HTML a partir de uma consulta de banco de dados), o cabeçalho Transfer-Encoding: chunked é usado. Isso permite que os dados sejam enviados em uma série de "pedaços" (chunks), com cada pedaço precedido por seu tamanho em hexadecimal. O cabeçalho Content-Length
deve ser omitido quando Transfer-Encoding: chunked
é usado.
Exemplo de Requisição HTTP/1.1 (POST):
POST /users HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 49
name=FirstName+LastName&email=bsmth%40example.com
Neste exemplo, POST /users HTTP/1.1
é a linha de requisição, indicando o método, o recurso e a versão do protocolo. Host
, Content-Type
e Content-Length
são os cabeçalhos que fornecem metadados sobre a requisição e o tipo e tamanho do corpo. A linha em branco separa os cabeçalhos do corpo, que contém os dados a serem enviados ao servidor.
Exemplo de Resposta HTTP/1.1 (201 Created):
HTTP/1.1 201 Created
Content-Type: application/json
Location: http://example.com/users/123
{
"message": "New user created",
"user": {
"id": 123,
"firstName": "Example",
"lastName": "Person",
"email": "bsmth@example.com"
}
}
Aqui, HTTP/1.1 201 Created
é a linha de status, informando a versão do protocolo, o código de sucesso e sua descrição. Content-Type
e Location
são cabeçalhos que descrevem o tipo de conteúdo do corpo e o URI do recurso recém-criado. O objeto JSON é o corpo da resposta, contendo os detalhes do usuário criado.
As versões mais recentes do HTTP introduziram mudanças significativas na forma como as mensagens são transmitidas, mas a semântica subjacente permanece a mesma. O HTTP/2, por exemplo, encapsula as mensagens em um formato binário, diferentemente do HTTP/1.1 que é baseado em texto. Embora essa mudança torne as mensagens mais difíceis de ler manualmente, ela permite otimizações cruciais, como a compressão de cabeçalhos (via HPACK), que reduz a redundância e o volume de dados transmitidos.
Mais importante, o HTTP/2 introduziu o multiplexing, permitindo que múltiplas requisições e respostas sejam enviadas e recebidas simultaneamente sobre uma única conexão TCP. Isso resolve o problema de "head-of-line blocking" do HTTP/1.1, onde uma requisição atrasada podia bloquear todas as subsequentes na mesma conexão. Em HTTP/2, as mensagens são envolvidas em frames binários e enviadas em streams numerados, permitindo que os streams sejam intercalados e priorizados.
Uma mudança chave em HTTP/2 é o uso de pseudo-cabeçalhos, que começam com :. Em requisições, eles incluem :method
(método HTTP), :scheme
(esquema URI), :authority
(autoridade URI) e :path
(caminho e query URI). Em respostas, :status
(código de status da resposta) é usado.
O HTTP/3, a versão mais recente, mantém a mesma semântica central (métodos, códigos de status, cabeçalhos) das versões anteriores, mas muda a camada de transporte de TCP para QUIC. QUIC é construído sobre UDP e é projetado para oferecer menor latência e melhor desempenho em redes não confiáveis. Ao contrário do TCP, onde a perda de um pacote pode bloquear todos os fluxos de dados na conexão, o QUIC trata a perda de pacotes e a retransmissão independentemente para cada "stream" (fluxo de dados), o que significa que apenas o fluxo afetado é bloqueado se ocorrer um erro.
A estabilidade semântica do HTTP, apesar das inovações na implementação e no transporte, é um testemunho de um princípio de design robusto: separar o "o quê" (a semântica da aplicação HTTP) do "como" (a mecânica de transporte e formatação). Para o desenvolvedor de aplicações, a transição entre as versões do HTTP é em grande parte transparente no nível da API. Os desenvolvedores continuam usando os mesmos métodos, esperando os mesmos códigos de status e manipulando cabeçalhos de forma conceitual, mesmo que a "fiação" subjacente tenha sido drasticamente otimizada para desempenho. Essa abordagem facilita a adoção de novas versões sem a necessidade de reescrever aplicações inteiras, garantindo a compatibilidade e a longevidade do ecossistema web.
4. Métodos HTTP: As Ações da Web
Os métodos HTTP, também conhecidos como "verbos HTTP", são um componente crucial da linha de início de uma requisição. Eles indicam a ação que o cliente deseja realizar em um recurso específico no servidor. Entender a semântica de cada método é fundamental para construir APIs web bem projetadas e intuitivas.
O que é Idempotência?
A idempotência é um conceito fundamental em sistemas distribuídos, especialmente no contexto do HTTP. De forma simples, uma operação é idempotente se você pode executá-la quantas vezes quiser, e o resultado final no servidor será sempre o mesmo após a primeira execução bem-sucedida.
Pense nisso como um interruptor de luz:
-
Ação Idempotente: Ligar a Luz
- Se a luz está apagada e você a liga, ela acende.
- Se a luz já está acesa e você a liga novamente, ela continua acesa. O estado final (luz acesa) é o mesmo, não importa quantas vezes você repita a ação de "ligar".
-
Ação Não Idempotente: Criar um Novo Pedido
- Se você clica em "Enviar Pedido" uma vez, um pedido é criado.
- Se você clica em "Enviar Pedido" novamente, um novo pedido é criado. Cada clique resulta em uma nova mudança no estado do sistema (um novo pedido).
Diagrama Simplificado de Idempotência:
+---------------------+
| AÇÃO IDEMPOTENTE
\+---------------------+
| Requisição 1: Ligar Luz -> Luz ACESA
| Requisição 2: Ligar Luz -> Luz ACESA (sem mudança)
| Requisição 3: Ligar Luz -> Luz ACESA (sem mudança)
|
| Resultado final é sempre o mesmo: Luz ACESA
\+---------------------+
+---------------------+
| AÇÃO NÃO IDEMPOTENTE
\+---------------------+
| Requisição 1: Criar Pedido -> Pedido #1 criado
| Requisição 2: Criar Pedido -> Pedido #2 criado (novo)
| Requisição 3: Criar Pedido -> Pedido #3 criado (novo)
|
| Resultado final muda a cada requisição
\+---------------------+
A idempotência é importante porque permite que as requisições sejam retentadas com segurança em caso de falhas de rede ou timeouts. Se uma requisição idempotente falhar, o cliente pode simplesmente tentar novamente sem se preocupar em causar efeitos colaterais indesejados (como criar duplicatas de dados).
GET
Propósito: Usado para recuperar dados de um recurso especificado. É o método mais comum na web, utilizado para carregar páginas HTML, imagens, arquivos CSS, dados de API, etc.
Características:
- Seguro: Sim. Uma requisição GET não deve causar efeitos colaterais no servidor, ou seja, não deve alterar o estado do servidor.
- Idempotente: Sim. Repetir a mesma requisição GET várias vezes terá sempre o mesmo resultado: o cliente receberá a mesma representação do recurso.
- Corpo: Não deve ter corpo na requisição. Dados adicionais (como filtros de busca) são enviados via URL como "query parameters".
Exemplo: GET /users/123 HTTP/1.1
(para buscar todas as informações do usuário com ID 123).
POST
Propósito: Usado principalmente para criar um novo recurso no servidor ou para enviar dados para processamento a um recurso existente sem necessariamente criar um novo. É comumente utilizado para enviar dados de formulários web.
Características:
- Seguro: Não. Pode causar efeitos colaterais, como a criação de um novo registro em um banco de dados.
- Idempotente: Não. Repetir a mesma requisição POST pode criar duplicatas do recurso ou ter efeitos diferentes a cada chamada. Por exemplo, clicar duas vezes em um botão de "enviar pedido" pode resultar em dois pedidos distintos.
- Corpo: Sim, geralmente contém os dados a serem enviados ao servidor.
Tipos de Conteúdo (HTML Forms): Para formulários HTML, o formato do corpo é determinado pelo atributo enctype
do <form>
:
-
application/x-www-form-urlencoded
: Chaves e valores são codificados como pareschave=valor
separados por&
, com caracteres não alfanuméricospercent-encoded
. Não é adequado para dados binários. -
multipart/form-data
: Cada valor é enviado como um bloco de dados separado por um delimitador definido pelo agente do usuário. Usado para upload de arquivos. -
text/plain
Exemplo: POST /users HTTP/1.1
com um corpo JSON contendo os dados de um novo usuário a ser criado.
PUT
Propósito: Usado para atualizar completamente um recurso existente ou para criar um novo recurso em um URI específico, se ele não existir. O cliente é responsável por fornecer a representação completa do recurso no corpo da requisição. Se o recurso já existe, ele é substituído pelo conteúdo do corpo.
Características:
- Seguro: Não.
- Idempotente: Sim. Repetir a mesma requisição PUT várias vezes resultará no recurso no mesmo estado final. Isso ocorre porque o cliente está sempre definindo o estado completo do recurso.
- Corpo: Sim, deve conter a representação completa do recurso.
Exemplo: PUT /users/123 HTTP/1.1
com um corpo JSON contendo todos os campos atualizados do usuário 123. Se um campo for omitido no corpo, ele será sobrescrito com um valor nulo, padrão, ou removido, dependendo da implementação do servidor.
PATCH
Propósito: Usado para aplicar modificações parciais a um recurso existente. Diferente do PUT, o cliente envia apenas as mudanças que deseja fazer, não a representação completa do recurso.
Características:
- Seguro: Não.
- Idempotente: Não necessariamente. A idempotência depende da implementação específica do servidor. Por exemplo, um PATCH que incrementa um contador não é idempotente, pois cada chamada altera o valor.
- Corpo: Sim, contém as instruções de modificação ou os campos a serem atualizados.
Formatos de Documento Patch: A especificação PATCH (RFC 5789) não define um formato específico. Formatos comuns baseados em JSON incluem:
-
JSON Merge Patch (RFC 7396): Mais simples, com a mesma estrutura do recurso original, mas incluindo apenas os campos a serem alterados ou adicionados. Um campo pode ser excluído definindo seu valor como null. Tipo de mídia:
application/merge-patch+json
-
JSON Patch (RFC 6902): Mais flexível, especificando as alterações como uma sequência de operações (adicionar, remover, substituir, copiar, testar). Tipo de mídia:
application/json-patch+json
.
Exemplo: PATCH /users/123 HTTP/1.1
com um corpo JSON contendo apenas { "status": "suspended" }
para mudar apenas o status do usuário, sem afetar outros campos.
DELETE
Propósito: Usado para remover o recurso especificado pelo URI.
Características:
- Seguro: Não.
- Idempotente: Sim. Tentar deletar um recurso que já foi deletado (ou que nunca existiu) não causa um efeito diferente no estado do servidor.
- Corpo: Não deve ter corpo na requisição, embora a especificação permita que tenha, mas sem semântica definida.
Exemplo: DELETE /users/123 HTTP/1.1
(para remover o usuário com ID 123).
Indo além deles
Além dos métodos mais comuns, outros métodos HTTP desempenham papéis específicos:
HEAD:
Semelhante ao GET, mas a resposta não inclui um corpo. É útil para obter metadados (cabeçalhos) de um recurso, como seu tamanho (Content-Length
) ou data de modificação (Last-Modified
), sem precisar transferir o conteúdo completo. Se a resposta
HEAD indicar que uma URL em cache não é mais atual, a cópia em cache é invalidada.
OPTIONS:
Usado para descrever as opções de comunicação (métodos HTTP permitidos, cabeçalhos suportados) disponíveis para o recurso alvo ou para o servidor como um todo. É frequentemente utilizado em requisições "preflight" de CORS (Cross-Origin Resource Sharing) para verificar se uma requisição complexa será permitida pelo servidor. O cliente pode especificar um URL específico ou um asterisco ( *) para o servidor inteiro.
CONNECT:
Usado para estabelecer um túnel a um servidor identificado pelo recurso de destino, geralmente para conexões HTTPS através de um proxy. O alvo da requisição CONNECT é único, incluindo apenas o host e a porta. É um método hop-by-hop, o que significa que proxies só o encaminharão se houver outro proxy de entrada antes do servidor de origem.
Atenção: Executar um proxy que suporta CONNECT deve ser restrito a um conjunto definido de portas conhecidas ou uma lista configurável de alvos seguros, pois tunelar para servidores arbitrários pode ser um risco de segurança (ex: retransmissão de spam).
TRACE:
Realiza um teste de loop-back da mensagem ao longo do caminho até o recurso de destino. É uma ferramenta de diagnóstico que permite ao cliente ver exatamente o que o servidor final recebeu. O corpo da resposta 200 OK
de um TRACE conterá a requisição HTTP exata que o servidor recebeu, com Content-Type: message/http
. O cliente não deve incluir corpo na requisição TRACE nem cabeçalhos com informações sensíveis (como credenciais ou cookies) para evitar exposição de dados.6 Historicamente, alguns servidores desabilitam TRACE devido a preocupações de segurança, como ataques de Cross-Site Tracing (XST).
A cerca da escolha dos métodos
A escolha do método HTTP não é meramente uma formalidade; ela comunica a intenção da operação ao sistema e aos desenvolvedores que consomem a API. Adotar a semântica correta dos métodos HTTP, conforme o padrão, promove a clareza, a previsibilidade e a interoperabilidade da API.
Por exemplo, há um debate sobre a escolha do método para "cancelar" uma reserva: usar POST /reservations/{id}/cancel
versus DELETE /reservations/{id}
. A preferência por DELETE neste cenário ocorre porque a lógica interna (deleção física versus marcação de cancelamento) deve ser encapsulada no backend, e o método DELETE comunica a intenção de "remover" o recurso. Isso significa que, ao ver um DELETE, um desenvolvedor imediatamente entende que a intenção é remover algo, independentemente de como o backend implementa essa "remoção".
Uma API que adere aos métodos HTTP de forma semântica é mais fácil de entender, manter e escalar. Ela se torna uma "interface" e um "contrato" mais robusto, permitindo que sistemas sejam desacoplados e evoluam independentemente. Desviar-se dessa semântica, por exemplo, usando POST para todas as operações de modificação/remoção, pode levar a APIs "chatty" (que exigem muitas requisições pequenas) e menos intuitivas, dificultando a integração e a depuração.
Melhores Práticas para Design de API RESTful:
Ao projetar APIs RESTful, é crucial usar os métodos HTTP de forma consistente com suas definições e com o recurso acessado.
- Nomes de Recursos: Use substantivos para os nomes dos recursos (ex: /orders em vez de /create-order), pois os métodos HTTP já implicam a ação.
- Nomes Plurais para Coleções: Use substantivos no plural para URIs que referenciam coleções (ex: /customers).
- URIs Hierárquicas: Organize URIs de coleções e itens hierarquicamente (ex: /customers/5 para um cliente específico).
- Relações Simples: Evite URIs excessivamente complexas que navegam por muitos níveis de relações (ex: /customers/1/orders/99/products). Mantenha os URIs relativamente simples e use links (HATEOAS) no corpo da resposta para acessar recursos relacionados.
- Evite APIs "Chatty": APIs que expõem muitos recursos pequenos exigem múltiplas requisições do cliente, aumentando a carga no servidor. Considere desnormalizar dados e combinar informações relacionadas em recursos maiores que podem ser recuperados em uma única requisição, equilibrando isso com o custo de buscar dados desnecessários.
Tabela: Comparativo de Métodos HTTP Comuns
Método | Propósito Principal | Idempotente? | Seguro? | Pode ter Corpo na Requisição? |
---|---|---|---|---|
GET | Recuperar dados | Sim | Sim | Não |
POST | Criar novo recurso / Enviar dados para processamento | Não | Não | Sim |
PUT | Atualizar recurso completamente / Criar recurso em URI específico | Sim | Não | Sim |
PATCH | Atualizar recurso parcialmente | Não (Depende da implementação) | Não | Sim |
DELETE | Remover recurso | Sim | Não | Não |
5. Códigos de Status HTTP: A Linguagem do Servidor
Quando um servidor responde a uma requisição HTTP, ele sempre inclui um código de status numérico de três dígitos na linha de status da resposta. Este código é uma mensagem padronizada que informa ao cliente o resultado da requisição. O primeiro dígito do código de status define a classe da resposta, fornecendo uma indicação geral do resultado:
1xx (Respostas Informacionais):
Indicam que a requisição foi recebida e o processo continua. São respostas provisórias que o cliente deve esperar antes de receber a resposta final.
Código HTTP | Nome | Descrição | Caso de Uso Típico |
---|---|---|---|
100 | Continue | O cliente deve continuar sua requisição ou ignorar se já concluída. | Pré-requisição de corpo grande (PUT/POST) |
101 | Switching Protocols | O servidor aceita a mudança de protocolo solicitada pelo cliente. | Upgrade para WebSocket/HTTP2 |
102 | Processing | O servidor está processando a requisição mas ainda não há resposta. | Operações assíncronas demoradas |
103 | Early Hints | Indica recursos que podem ser carregados antecipadamente. | Pré-carregamento de assets críticos |
2xx (Respostas de Sucesso):
A requisição foi recebida, compreendida e aceita com sucesso pelo servidor.
Código HTTP | Nome | Descrição | Caso de Uso Típico |
---|---|---|---|
200 | OK | A requisição foi bem-sucedida. O resultado real depende do método HTTP usado. Para GET, o corpo da resposta contém o recurso solicitado. Para POST, o corpo contém o resultado da ação. | Resposta padrão para requisições GET/POST bem-sucedidas |
201 | Created | A requisição foi bem-sucedida e um novo recurso foi criado como resultado. O URI do novo recurso é geralmente retornado no cabeçalho Location. | Criação de usuários, posts ou recursos em APIs REST |
202 | Accepted | A requisição foi aceita para processamento, mas o processamento ainda não foi concluído. A requisição pode ou não ser eventualmente atendida. | Processamentos assíncronos (envio de emails, transcodificação de vídeos) |
204 | No Content | O servidor processou a requisição com sucesso, mas não está retornando nenhum conteúdo no corpo da resposta. Isso é comum para requisições PUT ou DELETE bem-sucedidas. | Atualizações (PUT) ou remoções (DELETE) onde a resposta não é necessária |
3xx (Respostas de Redirecionamento)
Indicam que é necessária uma ação adicional para completar a requisição. Geralmente, o cliente precisa fazer uma nova requisição para um URI diferente.
Código HTTP | Nome | Descrição | Caso de Uso Típico |
---|---|---|---|
300 | Multiple Choices | Indica múltiplas opções para o recurso, das quais o cliente pode escolher (ex: diferentes formatos de vídeo). | Content negotiation (escolha entre JSON/XML) |
301 | Moved Permanently | O recurso foi movido permanentemente para um novo URI. Requisições futuras devem ser direcionadas para o novo URI. | Migração de domínio (http→https) |
302 | Found | O recurso está temporariamente em um URI diferente. O cliente deve continuar usando o URI original para requisições futuras (anteriormente "Moved Temporarily"). | Páginas em manutenção temporária |
303 | See Other | A resposta à requisição pode ser encontrada em outro URI usando o método GET. Comum após um POST (ou PUT/DELETE) para direcionar o cliente a um recurso recém-criado ou relacionado. | Redirecionamento pós-login |
304 | Not Modified | Indica que o recurso não foi modificado desde a versão especificada pelos cabeçalhos de requisição If-Modified-Since ou If-None-Match. O cliente pode usar sua cópia em cache. | Validação de cache (ETags) |
308 | Permanent Redirect | Semelhante ao 301, mas não permite que o método HTTP mude (ex: um POST para um recurso permanentemente redirecionado pode continuar suavemente como POST). | APIs REST com endpoints permanentes movidos |
4xx (Erros do Cliente):
Sinalizam que a requisição contém sintaxe incorreta ou não pode ser atendida pelo servidor devido a um problema percebido no cliente.
Código HTTP | Nome | Descrição | Caso de Uso Típico |
---|---|---|---|
400 | Bad Request | O servidor não pode ou não processará a requisição devido a um erro do cliente (ex: sintaxe malformada, roteamento enganoso). | JSON malformado em API, parâmetros inválidos |
401 | Unauthorized | A requisição requer autenticação do usuário. | Acesso sem token JWT, credenciais não fornecidas |
403 | Forbidden | O servidor entendeu a requisição, mas se recusa a autorizá-la (o cliente não tem permissão para acessar o recurso). | Usuário comum tentando acessar área administrativa |
404 | Not Found | O recurso solicitado não pôde ser encontrado no servidor. Este é um dos códigos de erro mais comuns. | Acesso a URL inexistente, recurso deletado |
405 | Method Not Allowed | O método HTTP usado na requisição não é suportado para o recurso identificado pelo URI. | Tentativa de POST em endpoint que só aceita GET |
5xx (Erros do Servidor):
Indicam que o servidor falhou em atender a uma requisição aparentemente válida.
Código HTTP | Nome | Descrição | Caso de Uso Típico |
---|---|---|---|
500 | Internal Server Error | Uma condição inesperada impediu o servidor de atender à requisição. | Erro não tratado no código do servidor, exceções inesperadas |
502 | Bad Gateway | O servidor, atuando como um gateway ou proxy, recebeu uma resposta inválida de um servidor upstream. | Falha em microsserviços, timeout em chamadas para APIs externas |
503 | Service Unavailable | O servidor não está pronto para lidar com a requisição. Isso é comum quando o servidor está sobrecarregado ou em manutenção. | Pico de tráfego, manutenção programada, reinicialização de serviços |
504 | Gateway Timeout | O servidor, atuando como gateway ou proxy, não recebeu uma resposta a tempo do servidor upstream. | Timeout em chamadas para serviços externos, conexões lentas entre servidores |
507 | Insufficient Storage | O servidor não pode armazenar a representação necessária para completar a requisição. | Espaço em disco insuficiente, cota de armazenamento excedida |
6. Recursos Essenciais do HTTP
HTTPS: Segurança e Criptografia (SSL/TLS)
HTTPS (HyperText Transfer Protocol Secure) é a versão criptografada do protocolo HTTP. Ele utiliza o TLS (Transport Layer Security), que é a evolução do SSL (Secure Sockets Layer), para criptografar toda a comunicação entre um cliente e um servidor. Essa conexão segura permite que os clientes troquem dados sensíveis, como informações bancárias ou detalhes de compras online, com o servidor de forma protegida.
Quando um site é protegido por HTTPS, um símbolo de cadeado aparece na barra de endereço do navegador, e os usuários podem clicar nele para ver detalhes do certificado, incluindo a autoridade emissora e o nome da empresa proprietária do site. A segurança do HTTPS não se limita a transações financeiras; ela protege todos os tipos de informações transferidas, incluindo dados de login, formulários e até mesmo a atividade do usuário em blogs informativos.
O Google tem promovido ativamente a adoção do HTTPS, recompensando sites seguros com classificações de busca mais altas desde 2014 e sinalizando sites não-SSL como "não seguros" no Chrome desde 2018.
O processo de estabelecimento de uma conexão segura via SSL/TLS é conhecido como "SSL handshake" e é invisível e instantâneo para os visitantes do site. Ele envolve três etapas principais:
- Autenticação: Para cada nova sessão, o navegador do usuário e o servidor trocam e validam os certificados SSL/TLS um do outro. Isso garante que o cliente está se comunicando com o servidor legítimo e vice-versa.
- Criptografia: O servidor compartilha sua chave pública com o navegador. O navegador então usa essa chave pública para criar e criptografar uma "chave pré-mestra". Este é o processo de troca de chaves.
- Descriptografia: O servidor descriptografa a chave pré-mestra usando sua chave privada, estabelecendo assim uma conexão segura e criptografada que permanece ativa durante toda a sessão.
Existem três níveis principais de verificação de identidade fornecidos por esses certificados digitais:
- Extended Validation (EV) SSL Certificates: Oferecem o mais alto nível de garantia de identidade, comumente usados por bancos e grandes varejistas. Fornecem garantias imediatas sobre a empresa proprietária do site ao clicar no cadeado.
- Organization Validated (OV) SSL Certificates: Opção de nível médio para sites que realizam negócios online. Requerem prova de propriedade do domínio e que a organização esteja registrada e legalmente responsável.
- Domain Validated (DV) SSL Certificates: O nível mais básico, exigindo apenas prova de controle sobre o nome de domínio, sem verificação de identidade. Recomendado apenas para sites não comerciais, como blogs, que não coletam informações pessoais.
Cookies HTTP: Gerenciamento de Sessão e Estado
Um cookie, também conhecido como web cookie ou browser cookie, é um pequeno pedaço de dados que um servidor envia para o navegador web de um usuário. O navegador pode armazenar esses cookies, criar novos, modificar os existentes e enviá-los de volta ao mesmo servidor com requisições subsequentes.
Os cookies são essenciais para que as aplicações web possam armazenar quantidades limitadas de dados e lembrar informações de estado, pois o protocolo HTTP é inerentemente sem estado. Isso significa que, por padrão, cada requisição HTTP é independente das anteriores, e o servidor não "lembra" de interações passadas com o mesmo cliente. Os cookies superam essa limitação, permitindo que as requisições sejam vinculadas ao estado do servidor, criando sessões.
Os cookies são principalmente usados para três propósitos:
Gerenciamento de sessão: Inclui lembrar o status de login do usuário, o conteúdo de um carrinho de compras, pontuações de jogos ou quaisquer outros detalhes relacionados à sessão do usuário que o servidor precise recordar. Por exemplo, em um sistema de login, após o usuário enviar suas credenciais, o servidor envia um cookie com um ID de sessão. Em requisições futuras, o navegador envia esse cookie de volta, permitindo que o servidor identifique o usuário e forneça uma experiência personalizada.
Personalização: Os cookies armazenam preferências do usuário, como idioma de exibição e temas de interface, permitindo uma experiência de usuário customizada.
Rastreamento: São usados para registrar e analisar o comportamento do usuário, o que pode ser utilizado para análises ou publicidade direcionada.
Os cookies são definidos pelo servidor através do cabeçalho Set-Cookie na resposta HTTP. Por exemplo:
Set-Cookie: yummy_cookie=chocolate
Quando o navegador faz uma nova requisição para o mesmo domínio, ele envia os cookies armazenados de volta ao servidor no cabeçalho Cookie. A vida útil de um cookie pode ser definida usando os atributos Expires
ou Max-Age
no cabeçalho Set-Cookie
. Cookies sem esses atributos são "cookies de sessão" e são excluídos quando a sessão do navegador termina.
Atributos de Escopo e Segurança de Cookies:
Domain: Especifica quais servidores podem receber um cookie. Se definido, o cookie está disponível para o domínio especificado e seus subdomínios. Se omitido, o cookie é enviado apenas para o servidor que o definiu, mas não para seus subdomínios.
Path: Indica um caminho de URL que deve existir na URL solicitada para que o cabeçalho Cookie seja enviado. Subdiretórios também correspondem.
Secure: Garante que o cookie só será enviado para o servidor se a requisição for feita através de HTTPS (conexão segura).
HttpOnly: Impede que scripts do lado do cliente (JavaScript) acessem o cookie, mitigando ataques de Cross-Site Scripting (XSS).
SameSite: Ajuda a prevenir ataques de Cross-Site Request Forgery (CSRF) controlando como os cookies são enviados em requisições cross-site.
É importante notar que, embora os cookies fossem historicamente usados para armazenamento geral de dados no lado do cliente, APIs de armazenamento modernas como a Web Storage API (localStorage e sessionStorage) e IndexedDB são agora recomendadas para esse fim. Essas APIs são projetadas especificamente para armazenamento, não enviam dados para o servidor e superam as limitações dos cookies, como limites de tamanho (geralmente 4KB por cookie) e problemas de desempenho (cookies são enviados com cada requisição)
Caching HTTP: Otimização de Desempenho
O caching HTTP envolve o armazenamento de uma resposta associada a uma requisição e a reutilização dessa resposta armazenada para requisições subsequentes. Essa capacidade de reutilização oferece várias vantagens significativas:
Respostas mais rápidas: Quando uma resposta é armazenada em cache, não há necessidade de enviar a requisição de volta ao servidor de origem. Quanto mais próximos o cliente e o cache estiverem, mais rápida será a resposta. Um exemplo comum é o cache do próprio navegador, que armazena recursos para acesso rápido.
Carga reduzida no servidor: Quando uma resposta pode ser reutilizada de um cache, o servidor de origem não precisa processar a requisição. Isso significa que ele evita tarefas como análise e roteamento da requisição, restauração de sessões baseadas em cookies, consulta a bancos de dados ou renderização de templates, reduzindo assim a carga sobre o servidor.
Existem dois tipos principais de caches na especificação de Caching HTTP: caches privados e caches compartilhados.4
Caches Privados: Um cache privado é vinculado a um cliente específico, tipicamente o cache de um navegador. Como a resposta armazenada não é compartilhada com outros clientes, um cache privado pode armazenar respostas personalizadas para aquele usuário. Se uma resposta contém conteúdo personalizado e deve ser armazenada apenas em um cache privado, a diretiva private deve ser especificada no cabeçalho
Cache-Control
.Caches Compartilhados: Um cache compartilhado está posicionado entre o cliente e o servidor e é capaz de armazenar respostas que podem ser compartilhadas entre múltiplos usuários. Exemplos incluem proxies de cache e CDNs (Content Delivery Networks). O comportamento desses caches pode ser controlado por cabeçalhos HTTP, como
Cache-Control
, que pode instruir proxies e clientes sobre o que armazenar em cache e por quanto tempo.
Cabeçalhos de Cache para Controle e Validação:
Cache-Control: O cabeçalho mais importante para controlar o comportamento do cache. Pode incluir diretivas como:
-
no-store
: Não armazena a resposta em nenhum cache. -
no-cache
: O cache deve revalidar a resposta com o servidor de origem antes de usá-la. -
max-age=<segundos>
: Define o tempo máximo que uma resposta pode ser considerada "fresca" (utilizável sem revalidação). -
must-revalidate
: O cache deve revalidar a resposta com o servidor de origem após a expiração da max-age. -
private
: Indica que a resposta é específica do usuário e só deve ser armazenada em caches privados.
Last-Modified: Um cabeçalho de resposta que especifica a última vez que o conteúdo foi modificado, em formato de timestamp.
ETag (Entity Tag): Um identificador único para uma versão específica de um recurso. É um valor gerado pelo servidor (geralmente um hash do conteúdo ou timestamp de modificação) que muda toda vez que o recurso é modificado.
Validação de Cache: Quando um cliente tem uma cópia em cache de um recurso com um ETag ou Last-Modified, ele pode enviar uma requisição condicional ao servidor usando os cabeçalhos If-None-Match (com o ETag armazenado) ou If-Modified-Since (com o Last-Modified armazenado). Se o recurso não mudou, o servidor responde com 304 Not Modified, e o cliente usa sua cópia em cache, economizando largura de banda.
Prevenção de "Mid-air Collisions": ETags também ajudam a prevenir que atualizações simultâneas de um recurso se sobrescrevam. Ao enviar uma requisição PUT ou PATCH, o cliente pode incluir o ETag do recurso que ele modificou no cabeçalho If-Match. Se o ETag no servidor não corresponder (indicando que o recurso foi modificado por outra pessoa), o servidor pode retornar um erro 412 Precondition Failed.
7. HTTP na Prática: Requisições em Java
Para interagir com serviços web e APIs HTTP em Java, a partir do Java 11, a API java.net.http.HttpClient
é a ferramenta padrão e recomendada. Esta API moderna oferece uma abordagem fluente e assíncrona para fazer requisições HTTP, além de suportar HTTP/2 por padrão e ser construída com base no padrão de design Builder para criar objetos imutáveis como HttpClient
e HttpRequest
.
Configuração Básica do HttpClient
A classe HttpClient é a raiz de todo o cliente HTTP. Ela é imutável e sua configuração (como proxy, configuração SSL, redirecionamentos e autenticação) é feita através da classe HttpClient.Builder
import java.net.http.HttpClient;
import java.time.Duration; // Para configurar timeouts
public class HttpClientConfig {
public static void main(String args) {
// Criando um HttpClient com configurações personalizadas
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2) // Usar HTTP/2 por padrão
.followRedirects(HttpClient.Redirect.NORMAL) // Seguir redirecionamentos normalmente
.connectTimeout(Duration.ofSeconds(10)) // Timeout de conexão de 10 segundos
//.proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 8080))) // Configurar proxy
//.authenticator(new Authenticator() {... }) // Configurar autenticação
.build();
System.out.println("HttpClient configurado com sucesso.");
}
}
Exemplo 1: Requisição GET Síncrona
Para realizar uma requisição GET e obter o corpo da resposta como uma String, o processo é direto. Primeiro, uma instância de HttpClient
é criada. Em seguida, um HttpRequest
é construído, especificando o URI e o método GET (que é o padrão). Finalmente, o método send()
do cliente é invocado, passando a requisição e um BodyHandler
que define como o corpo da resposta deve ser processado. BodyHandlers.ofString()
é usado para converter o corpo da resposta em uma String.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
public class SimpleHttpGet {
public static void main(String args) throws Exception {
// 1. Criar uma instância de HttpClient (pode ser reutilizada para múltiplas requisições)
HttpClient client = HttpClient.newHttpClient();
// 2. Construir a requisição HTTP GET
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/todos/1"))
.GET() // Método GET é o padrão, mas pode ser explicitado
.header("Accept", "application/json") // Exemplo de cabeçalho: aceitar JSON
.build();
// 3. Enviar a requisição e receber a resposta de forma síncrona
// BodyHandlers.ofString() converte o corpo da resposta em String
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
// 4. Imprimir o código de status e o corpo da resposta
System.out.println("Status Code: " + response.statusCode());
System.out.println("Response Headers: " + response.headers().map());
System.out.println("Response Body: " + response.body());
// Exemplo de como acessar dados JSON (requer biblioteca como Jackson)
// ObjectMapper mapper = new ObjectMapper();
// JsonNode root = mapper.readTree(response.body());
// System.out.println("Title from JSON: " + root.path("title").asText());
}
}
O BodyHandler é invocado quando o código de status e os cabeçalhos da resposta estão disponíveis, antes que os bytes do corpo da resposta sejam recebidos. Ele é responsável por criar um BodySubscriber que recebe fluxos de dados com backpressure não bloqueante e pode converter os bytes do corpo da resposta em um tipo Java de nível superior.
HttpResponse.BodyHandlers
oferece métodos de fábrica estáticos para criar instâncias de BodyHandler
, alguns acumulando bytes em memória (ofString
, ofByteArray
), outros transmitindo dados conforme chegam (ofFile
, ofInputStream
).
Exemplo 2: Requisição POST com Corpo (JSON)
Requisições POST geralmente incluem um corpo de dados, como um objeto JSON, para enviar informações ao servidor. Para isso, utiliza-se um HttpRequest.BodyPublisher
para fornecer o corpo da requisição. BodyPublishers.ofString()
é comumente usado para enviar dados como uma String (por exemplo, JSON). É crucial definir o cabeçalho Content-Type
para application/json
para informar ao servidor o formato do corpo enviado.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper; // Necessário adicionar a dependência Jackson
public class SimpleHttpPostJson {
public static void main(String args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
// Dados a serem enviados no corpo da requisição (exemplo JSON)
Map<Object, Object> data = Map.of(
"title", "foo",
"body", "bar",
"userId", 1
);
// Converter o mapa para uma string JSON
ObjectMapper objectMapper = new ObjectMapper();
String requestBody = objectMapper.writeValueAsString(data);
// Construir a requisição HTTP POST com corpo JSON
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/posts"))
.header("Content-Type", "application/json") // Informar ao servidor que o corpo é JSON
.POST(BodyPublishers.ofString(requestBody)) // Definir o corpo da requisição
.build();
// Enviar a requisição e receber a resposta de forma síncrona
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
// Imprimir o código de status e o corpo da resposta
System.out.println("Status Code: " + response.statusCode());
System.out.println("Response Body: " + response.body());
}
}
**Nota:* Para o exemplo acima, é necessário adicionar a biblioteca Jackson (ou outra biblioteca JSON) ao seu projeto para
ObjectMapper
. Se estiver usando Maven, adicione:*
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.0</version>
</dependency>
Um BodyPublisher
é um publicador de reactive-stream que publica fluxos de corpo de requisição sob demanda. HttpRequest.Builder
fornece métodos como Builder::POST
e Builder::PUT
para definir um BodyPublisher
. A classe HttpRequest.BodyPublishers
oferece métodos de fábrica estáticos para tipos de dados comuns, como ofString
, ofByteArray
e ofFile
.
Exemplo 3: Requisição Assíncrona (GET)
A API HttpClient
também suporta requisições assíncronas, que são ideais para aplicações que precisam manter a responsividade enquanto aguardam respostas de rede. O método sendAsync()
retorna um CompletableFuture
, que permite processar a resposta quando ela estiver disponível, sem bloquear o thread principal.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class AsyncHttpGet {
public static void main(String args) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/comments/1"))
.GET()
.build();
// Enviar a requisição de forma assíncrona
CompletableFuture<HttpResponse<String>> futureResponse = client
.sendAsync(request, BodyHandlers.ofString());
// Processar a resposta quando ela estiver disponível
futureResponse.thenApply(HttpResponse::body)
.thenAccept(body -> System.out.println("Async Response Body: " + body))
.exceptionally(e -> {
System.err.println("Erro na requisição assíncrona: " + e.getMessage());
return null;
});
// Este print pode aparecer antes da resposta assíncrona, demonstrando a não-bloqueabilidade
System.out.println("Requisição assíncrona enviada. Aguardando resposta...");
// Para manter o programa principal em execução tempo suficiente para a resposta assíncrona
// Em uma aplicação real, você teria um loop de eventos ou outro mecanismo de espera
try {
// Bloqueia o thread principal para garantir que a resposta assíncrona seja processada
// Em aplicações reais, você não bloquearia o thread principal dessa forma.
// Usaria um framework web ou um executor de threads para gerenciar o ciclo de vida.
futureResponse.get(); // Espera a conclusão do CompletableFuture
} catch (InterruptedException | ExecutionException e) {
System.err.println("Erro ao esperar pela resposta assíncrona: " + e.getMessage());
Thread.currentThread().interrupt();
}
}
}
Este exemplo demonstra como a requisição é enviada e o programa continua sua execução, com a resposta sendo tratada em um callback quando o CompletableFuture
é concluído. Isso é fundamental para construir aplicações Java eficientes e responsivas que interagem com a web.
Exemplo 4: Requisição PUT para Atualização
Para atualizar um recurso existente, o método PUT é utilizado. Ele requer que o corpo da requisição contenha a representação completa do recurso.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
public class SimpleHttpPut {
public static void main(String args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
// Dados completos para atualizar o recurso (exemplo JSON)
Map<Object, Object> data = Map.of(
"id", 1, // O ID do recurso a ser atualizado
"title", "foo updated",
"body", "bar updated",
"userId", 1
);
ObjectMapper objectMapper = new ObjectMapper();
String requestBody = objectMapper.writeValueAsString(data);
HttpRequest request = HttpRequest.newBuilder()
// URI do recurso específico
.uri(URI.create("https://jsonplaceholder.typicode.com/posts/1"))
.header("Content-Type", "application/json")
// Usar PUT com o corpo completo
.PUT(BodyPublishers.ofString(requestBody))
.build();
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
System.out.println("Status Code (PUT): " + response.statusCode());
System.out.println("Response Body (PUT): " + response.body());
}
}
Exemplo 5: Requisição DELETE
Para remover um recurso, o método DELETE é empregado. Geralmente, requisições DELETE não possuem corpo.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
public class SimpleHttpDelete {
public static void main(String args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
// URI do recurso a ser deletado
.uri(URI.create("https://jsonplaceholder.typicode.com/posts/1"))
// Usar o método DELETE
.DELETE()
.build();
// Para DELETE, muitas vezes não há corpo de resposta, então BodyHandlers.discarding() é comum
HttpResponse<Void> response = client.send(request, BodyHandlers.discarding());
System.out.println("Status Code (DELETE): " + response.statusCode());
// Para 204 No Content, o corpo da resposta é vazio.
// System.out.println("Response Body (DELETE): " + response.body());
}
}
Tratamento de Erros e Timeouts
A API HttpClient permite configurar timeouts e lidar com exceções de rede.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.io.IOException;
import java.net.ConnectException;
import java.net.http.HttpTimeoutException;
import java.time.Duration;
public class ErrorHandlingExample {
public static void main(String args) {
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5)) // Timeout para estabelecer a conexão
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:9999/unreachable-service")) // Um serviço que provavelmente não existe
.timeout(Duration.ofSeconds(3)) // Timeout para a requisição completa
.GET()
.build();
try {
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
System.out.println("Status Code: " + response.statusCode());
System.out.println("Response Body: " + response.body());
} catch (ConnectException e) {
System.err.println("Erro de conexão: O serviço está inacessível ou o host não existe. " + e.getMessage());
} catch (HttpTimeoutException e) {
System.err.println("Erro de timeout: A requisição excedeu o tempo limite. " + e.getMessage());
} catch (IOException e) {
System.err.println("Erro de I/O: Problema na comunicação de rede. " + e.getMessage());
} catch (InterruptedException e) {
System.err.println("Requisição interrompida: " + e.getMessage());
Thread.currentThread().interrupt(); // Restaura o status de interrupção
} catch (Exception e) {
System.err.println("Ocorreu um erro inesperado: " + e.getMessage());
}
}
}
É possível configurar o tamanho do pool de conexões HTTP/1.1 e o tempo de vida de conexões ociosas usando propriedades do sistema como jdk.httpclient.connectionPoolSize
e jdk.httpclient.keepalive.timeout
. Para HTTP/2, há propriedades específicas como jdk.httpclient.connectionWindowSize
e jdk.httpclient.maxframesize
para controle de fluxo e tamanho de frames.
Conclusões
O Hypertext Transfer Protocol (HTTP) é, sem dúvida, a fundação invisível sobre a qual a World Wide Web foi construída e continua a evoluir. Compreender seus princípios, desde o modelo cliente-servidor e a estrutura das mensagens até a semântica dos métodos e os códigos de status, é fundamental para qualquer pessoa que deseje desenvolver ou mesmo apenas entender como a internet funciona.
A análise demonstrou que o HTTP é um protocolo notavelmente extensível e resiliente. Sua arquitetura em camadas permite que inovações significativas ocorram nas camadas de transporte (como a transição para QUIC no HTTP/3) sem que a lógica de aplicação e a interface para desenvolvedores sejam drasticamente alteradas. Essa estabilidade semântica, apesar das profundas otimizações de desempenho subjacentes, é um testemunho da robustez do seu design.
A evolução do HTTP, desde o simples HTTP/0.9 até o complexo e otimizado HTTP/3, reflete a crescente demanda por velocidade, segurança e interatividade na web. A criptografia via HTTPS, a gestão de estado através de cookies e a otimização de desempenho por meio de caching são exemplos de como o protocolo se adaptou para atender às necessidades de aplicações web cada vez mais sofisticadas.
Para desenvolvedores, a aderência à semântica dos métodos HTTP no design de APIs RESTful não é apenas uma questão de convenção, mas um pilar para a construção de sistemas claros, manuteníveis e interoperáveis. A capacidade de utilizar ferramentas modernas como a API java.net.http.HttpClient
em Java simplifica a interação com o ecossistema HTTP, permitindo que os desenvolvedores construam aplicações robustas e eficientes que se comunicam perfeitamente com a vasta rede de serviços web. Em suma, o HTTP permanece como um pilar essencial da comunicação digital, e seu entendimento aprofundado é uma habilidade valiosa no cenário tecnológico atual.
Refêrencias
Documentação Geral
Caching e Mensagens
Métodos e Códigos de Status
Segurança e HTTPS
Headers e Cookies
- HTTP headers - MDN Web Docs
- Transfer-Encoding header - MDN Web Docs
- Using HTTP cookies - MDN Web Docs
- Cookie - MDN Web Docs
Top comments (0)