# HTTP além das abstrações: Saia do automático e entenda o protocolo fundamental da Web

O **HTTP (Hypertext Transfer Protocol)** continua sendo o protocolo central da web moderna. Embora muitos desenvolvedores lidem com ele de forma indireta ao construir aplicativos, entender seu funcionamento detalhado é indispensável para otimizar e solucionar problemas em sistemas complexos. Apesar de a imensa maioria não necessitar de fato trabalhar com esse "baixo nível", trouxe este artigo para ir além dos conceitos básicos, oferecendo uma visão mais avançada do protocolo HTTP, desde a estrutura das requisições e respostas, até sua relação com o **TCP (Transmission Control Protocol)**.

> Disclaimer: A ideia deste artigo é tentar desvendar o funcionamento da abstração deste protocolo, porém ainda teremos que assumir algumas abstrações, como o protocolo TCP e o restante das camadas do modelo OSI.
> 
> Lembre-se: abstrações são necessárias e imperfeitas.

## 1\. Introdução

O **HTTP** é um protocolo de comunicação de rede baseado em texto que opera na **camada de aplicação do modelo OSI**. Ele é projetado para facilitar a troca de informações entre clientes e servidores web. Sua simplicidade e flexibilidade permitiram que ele se tornasse um protocolo fundamental para a comunicação na internet.

![](https://www3.ntu.edu.sg/home/ehchua/programming/webprogramming/images/HTTP.png align="center")

<small>Fonte: ntu.edu.sg</small>

A comunicação HTTP segue um modelo de requisição/resposta, em que o cliente faz uma requisição HTTP ao servidor e este retorna uma resposta, ambos compostos por **cabeçalhos** e **corpo de dados**.

## 2\. Estrutura de uma Requisição HTTP

Uma requisição HTTP consiste em três partes principais: **linha de requisição**, **cabeçalhos** e, opcionalmente, o **corpo**. Vamos explorar mais detalhadamente cada uma delas, e vale destacar que, como o próprio nome do protocolo sugere, estamos essencialmente trabalhando com texto (JSON, XML, Plain Text, etc.).

> Spoiler: Essa mentirinha de trabalhar somente com texto será desmentida no item 4.

### 2.1 Linha de Requisição

A linha de requisição define o que o cliente está solicitando ao servidor e tem a seguinte estrutura:

```plaintext
<Método HTTP> | <URL> | <Versão do Protocolo>
```

* &lt;Método HTTP&gt;: Indica a ação que o cliente deseja realizar.
    
    * GET: Recupera um dado.
        
    * POST: Envia dados ao servidor para serem processados.
        
    * PUT: Atualiza um recurso existente.
        
    * PATCH: Atualiza um recurso existente parcialmente.
        
    * DELETE: Remove um recurso.
        
    * HEAD: Recupera apenas os cabeçalhos de uma resposta (sem o corpo).
        
    * OPTIONS: Verifica quais métodos estão disponíveis para o recurso.
        
    * CONNECT: Estabelece um túnel de comunicação entre o cliente e o servidor.
        
    * TRACE: Utilizado para fazer um loopback de uma requisição ao servidor, permitindo ao cliente ver as modificações ou roteamento ao longo do caminho. **Não é um método adotado** por questões de segurança.
        
* &lt;URL&gt;: A localização do recurso solicitado.
    
* &lt;Versão do Protocolo&gt;: Especifica a versão do protocolo HTTP, como `HTTP/1.1` ou `HTTP/2.0`.
    

### 2.2 Cabeçalhos

Os cabeçalhos de uma requisição HTTP possuem informações adicionais que ajudam o servidor a processar a solicitação. Alguns dos cabeçalhos mais importantes incluem:

* **Host**: Especifica o nome do servidor ao qual a requisição está sendo enviada.
    
* **Authorization**: Especifica as credenciais para autenticar um User-Agent com o servidor.
    
* **User-Agent**: Informa qual é o cliente que está fazendo a requisição, como o navegador ou um dispositivo específico.
    
* **Content-Type**: Indica o tipo de dados que estão sendo enviados no corpo da requisição (por exemplo, `application/json` para o tipo JSON).
    
* **Accept**: Informa ao servidor sobre os tipos de dados (MIME-type) que podem ser enviados de volta.
    
* **Content-Length**: Indica o tamanho do corpo da mensagem, em decimal, enviado ao destinatário.
    

```plaintext
Host: vilaca.dev
Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l
User-Agent: Mozilla/5.0
Content-Type: application/json
Accept: application/json
Content-Length: 1154
```

### 2.3 Corpo da Requisição

O corpo da requisição é usado em métodos como **POST** ou **PUT**, onde os dados precisam ser enviados ao servidor, como ao submeter um formulário ou enviar dados em formato JSON. Em uma requisição **POST**, o corpo pode conter algo como o exemplo abaixo:

```json
{ "name": "John Doe" }
```

## 3\. Estrutura de uma Resposta HTTP

Semelhante à requisição, uma resposta HTTP também é composta por uma **linha de status**, **cabeçalhos** e, opcionalmente, um **corpo**.

### 3.1 Linha de Status

A linha de status indica o resultado da requisição e segue a seguinte estrutura:

```plaintext
<Versão do Protocolo>| <Código de Status> | <Descrição>
```

* &lt;Versão do Protocolo&gt;: Especifica a versão do protocolo HTTP, exatamente como na requisição.
    
* &lt;Código de Status&gt;: É um código numérico que indica o resultado da requisição. Por exemplo:
    
    * 200: (OK) A requisição foi bem-sucedida.
        
    * 404: (Not Found) O recurso solicitado não foi encontrado.
        
    * 500: (Internal Server Error) Ocorreu um erro no servidor.
        
    * Veja a lista completa em [https://developer.mozilla.org/pt-BR/docs/Web/HTTP/Status](https://developer.mozilla.org/pt-BR/docs/Web/HTTP/Status).
        
* &lt;Descrição&gt;: É uma mensagem textual que descreve o código de status de forma legível por humanos.
    

```plaintext
HTTP/1.1 200 OK
```

### 3.2 Cabeçalhos

Os cabeçalhos de uma resposta contêm informações sobre os dados que estão sendo enviados de volta ao cliente. Alguns cabeçalhos comuns são:

* Content-Type: Indica o tipo de dados que estão sendo retornados, como `text/html` ou `application/json`.
    
* Content-Length: Indica o tamanho do corpo da resposta, em bytes.
    

```plaintext
Content-Type: application/json
Content-Length: 123
```

### 3.3 Corpo da Resposta

O corpo da resposta contém os dados propriamente ditos que o servidor está retornando, de acordo com o tipo informado no cabeçalho.

```json
{
  "id": 1,
  "name": "John Doe",
  "email": "john.doe@vilaca.dev"
}
```

## 4\. HTTP e os tipos diferente de texto

Embora o HTTP tenha sido inicialmente projetado para **transportar texto**, ele é suficientemente flexível para transmitir qualquer tipo de dado, incluindo **imagens**, **áudio**, **vídeo** e outros arquivos binários. Isso é possível porque o HTTP trabalha com o conceito de **tipos de mídia** (MIME types), permitindo a identificação e transporte de diferentes formatos de dados de forma eficiente.

1. **Content-Type**: Quando o cliente faz uma requisição (como para baixar uma imagem ou um arquivo de áudio), o servidor responde com um **cabeçalho HTTP** que inclui o campo **Content-Type**. Esse cabeçalho informa ao cliente qual o tipo de dado que está sendo enviado. Por exemplo: `Content-Type: image/jpeg`, `Content-Type: audio/mpeg`, `Content-Type: video/mp4`, `Content-Type: application/pdf`, etc.
    
2. **Transmissão de Dados Binários**: Apesar do HTTP ser essencialmente um protocolo baseado em texto, ele é capaz de **transmitir dados binários** de forma transparente. Isso significa que o corpo da resposta HTTP pode conter qualquer tipo de dados, não apenas texto. Quando o navegador ou cliente HTTP recebe esses dados binários, eles os tratam de acordo com o **Content-Type** especificado.
    
3. **Interpretação no Cliente**: Ao receber o conteúdo (como uma imagem), o navegador ou aplicativo cliente **interpreta o arquivo de acordo com o tipo de dado** informado no cabeçalho e exibe ou armazena ele corretamente.
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1727222968161/9420f69f-da80-42e4-aeb4-c4417ce44ebf.png align="center")

No exemplo acima, o servidor está informando que o conteúdo enviado é uma imagem no formato JPEG e possui um tamanho de exatos 102.400 bytes. O cliente processa a imagem de acordo com essa informação.

## 5\. A relação entre HTTP e TCP

Embora o HTTP seja responsável pela comunicação de **dados legíveis** para humanos, sejam eles usuários finais ou desenvolvedores, ele depende do **TCP (Transmission Control Protocol)**, que trabalha na camada de transporte (4), para garantir que esses dados sejam transmitidos de forma confiável entre o cliente e o servidor.

> Ok sejamos sinceros com mais uma mentira do bem sobre o HTTP depender do TCP. Isso não é verdade porque embora o TCP seja o protocolo de comunicação mais associado ao HTTP, existem outros protocolos de transporte que também podem ser usados para suportar o HTTP, como o [**QUIC**](https://datatracker.ietf.org/doc/rfc9000/).

### 5.1 Função do TCP

O TCP oferece uma comunicação confiável o suficiente entre duas máquinas, garantindo que os dados sejam entregues de maneira correta e na ordem apropriada. Ele é responsável por:

* **Segmentação e reordenação** para dividir a mensagem HTTP em pequenos pacotes e garantir que eles cheguem na ordem correta ao destino.
    
* **Controle de erros** para verificar se os pacotes chegaram intactos e solicitar a retransmissão, se necessário.
    
* **Controle de fluxo** para garantir que o remetente não sobrecarregue o destinatário enviando dados mais rapidamente do que podem ser processados.
    

### 5.2 Three-way Handshake

Antes de enviar uma requisição, o TCP estabelece uma conexão usando o processo conhecido como **três vias**:

1. **SYN**: O cliente envia um pacote de sincronização para o servidor.
    
2. **SYN-ACK**: O servidor responde com um pacote de sincronização e reconhecimento.
    
3. **ACK**: O cliente confirma o recebimento do pacote de sincronização.
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1727224139245/7b0fe882-7820-45bc-bf48-e83dda1d0758.png align="center")

Somente após o handshake, a comunicação HTTP é iniciada.

## 6\. Implementando uma requisição HTTP

Para implementar uma requisição HTTP de forma completamente crua, ou seja, implementando o protocolo manualmente, incluindo detalhes como o **handshake**, **envio de pacotes**, **controle de acknowledgment** e gerenciamento de conexões, a melhor linguagem seria uma de baixo nível, como **C** ou **C++**. Essas linguagens permitem o controle detalhado sobre os **sockets** e as operações de rede, sem abstrações que escondem os detalhes do protocolo HTTP. Mas ficando na minha zona de conforto eu utilizarei **Go**, que também é possível implementar em “baixo nível”, embora tenha menos controle fino comparado a C quando se trata de raw sockets.

Criaremos um programa simples que irá se comunicar com a REST API [https://httpbin.org](https://httpbin.org/get). Consumiremos a rota `GET /get` que apenas retorna um JSON com informações relacionadas a própria requisição. Abaixo um diagrama de sequência exemplificando o programa.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1727301795662/16542f16-d48d-492a-809f-e1058061804e.png align="center")

Primeiramente, estabeleceremos uma conexão TCP com o servidor. Para isso, precisamos do endereço IP do servidor. No dia em que escrevo este artigo o endereço de IP do host [httpbin.org](http://httpbin.org) é “23.21.73.249“, porém pode mudar; para verificar, basta utilizar uma ferramenta como [nslookup](https://learn.microsoft.com/pt-br/windows-server/administration/windows-commands/nslookup). Podemos importar o pacote `net`, que faz parte da biblioteca padrão da linguagem Go, e utilizar a função `Dial` para estabelecer essa conexão.

```go
conn, err := net.Dial("tcp", "23.21.73.249:80")
if err != nil {
  fmt.Println("Erro ao conectar:", err)
  os.Exit(1)
}
defer conn.Close()
```

Com a conexão estabelecida, o próximo passo é criar uma requisição HTTP para este servidor. Para isso, basta se comunicar com os cabeçalhos aprendidos anteriormente e enviar para o servidor.

```go
request := "GET /get HTTP/1.1\r\nHost: httpbin.org\r\n\r\n"

_, err = conn.Write([]byte(request))
if err != nil {
  fmt.Println("Erro ao enviar dados:", err)
  os.Exit(1)
}
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Tanto na <a target="_blank" rel="noopener noreferrer nofollow" href="https://datatracker.ietf.org/doc/html/rfc2616" style="pointer-events: none">RFC 2616</a> quanto na <a target="_blank" rel="noopener noreferrer nofollow" href="https://datatracker.ietf.org/doc/html/rfc7230" style="pointer-events: none">RFC 7230</a> indica o uso de CRLF, ou seja, <em>carriage return</em> (\r) seguido de <em>line feed</em> (\n) para indicar a quebra de linha. Um dos motivos é a compatibilidade, porém não é certeza de que não funcionará apenas com “\n”.</div>
</div>

Prontinho, nesse momento o servidor se comunicará de volta com a gente e precisamos receber os dados enviados por ele. Como já vimos que a leitura deve ser feita de forma sequencial, podemos utilizar a interface Reader padrão do *io* do Go para fazer a leitura dos dados que o servidor nos manda.

```go
reader := bufio.NewReader(conn)

statusLine, err := reader.ReadString('\n')
if err != nil {
	fmt.Println("Error reading", err.Error())
	os.Exit(1)
}
```

Neste ponto já temos armazenado na variável `statusLine` a linha referente ao resultado da nossa requisição. Podemos então seguir para as próximas linhas dos headers, até encontrarmos uma linha em branco que indica o fim.

```go
headers := make(map[string]string)

for {
	line, err := reader.ReadString('\n')
	if err != nil {
		fmt.Println("Erro ao ler os headers:", err)
		os.Exit(1)
	}

	// Se a linha estiver vazia, significa o fim dos headers
	if line == "\r\n" {
		break
	}

	headerParts := strings.SplitN(line, ": ", 2)
	if len(headerParts) == 2 {
		key := strings.TrimSpace(headerParts[0])
		value := strings.TrimSpace(headerParts[1])
		headers[key] = value
	}
}
```

Finalizado a leitura dos headers, já temos como saber qual o tamanho dos dados que estão no corpo da mensagem, o tipo de dado, etc. Com isso, fica mais fácil alocar um espaço fixo na memória do tamanho exato do conteúdo da mensagem, por exemplo.

```go
contentLength, err := strconv.Atoi(headers["Content-Length"])
if err != nil {
	fmt.Println("Erro no Content-Length:", err)
	os.Exit(1)
}
```

Com isso, podemos criar um array de bytes do tamanho informado nos headers e ler todo o conteúdo de uma só vez.

```go
body := make([]byte, contentLength)

_, err = reader.Read(body)
if err != nil {
	fmt.Println("Erro ao ler o corpo da resposta:", err)
	os.Exit(1)
}
```

Implementação finalizada. Neste ponto a comunicação ocorreu com sucesso e conseguimos realizar a leitura do status, headers e body. Seguindo as especificações já aprendidas anteriormente, essas informações são suficientes para trabalhar com quaisquer tipo de conteúdo. Veja todo o código fonte [neste gist](https://gist.github.com/davidpvilaca/523bcf6f4fc6157eecd6680ee9584980).

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1727304296011/893a4f03-c8de-4c01-a7da-6015054a01d8.png align="center")

## 7\. Conclusão

O HTTP é um protocolo simples na superfície, mas suas funcionalidades avançadas e nuances o tornam uma peça fundamental no funcionamento da web. Entender suas particularidades, desde o ciclo de requisição/resposta até as diferenças entre versões, permite que desenvolvedores otimizem suas aplicações e resolvam problemas de forma mais eficaz, como saber quando não utilizar esse protocolo. Embora muitos dos detalhes de baixo nível, como o TCP, sejam gerenciados automaticamente por servidores e abstrações, a compreensão de como essas camadas interagem é crucial para a criação de sistemas web robustos e eficientes.

Espero ter contribuído positivamente para o seu conhecimento sobre os conceitos por trás das abstrações que utilizamos no dia a dia. Continue buscando ter uma base sólida para enfrentar problemas realmente complexos de desenvolvimento e sair do básico de CRUD.

Sugestões para estudos futuros:

1. **Segurança em Protocolos HTTP**: Qual a diferença entre HTTP e HTTPS? Como me proteger contra ataques comuns (por exemplo: XSS e CSRF)? Qual a importância de certificados SSL/TLS?
    
2. **Evolução do Protocolo HTTP (1.1, 2 e 3)**: O que evoluiu? Houve melhoria de desempenho, segurança e/ou eficiência? Essas mudanças impactam a experiência de usuário e/ou o desenvolvimento de aplicações web?
    
3. **Desempenho de Aplicações Web e HTTP**: Configurações e/ou seguir algumas práticas recomendadas pela comunidade pode afetar o desempenho? Como e para que utilizar cache? Como monitorar o desempenho em uma comunicação HTTP?
