Servidores: Era tudo um grande WhatsApp 😦

Afonso Lucas
Diário Backend
Published in
6 min readJul 12, 2023

O Express e o node me iludiram, mas agora eu sei a verdade sobre o protocolo HTTP. Talvez fosse algo a se esperar mas na época eu era bem leigo — BTW, ainda sou 🥲.

Quando comecei a estudar protocolos de comunicação e redes, fiquei maravilhado — e também um pouco desesperado, era muita coisa para estudar. Depois de um tempo, decidi me aprofundar no protocolo HTTP, até porque sempre tive interesse em desenvolver APIs. A propósito, recomendo ler meu artigo sobre HTTP e Web antes de continuar aqui (se você não entender muito do assunto, claro).

Sempre me diverti brincando com JavaScript e Express, construir APIs era uma tarefa relativamente fácil, mas sempre senti que não entendia completamente o que acontecia nos bastidores. Eu tinha um servidor que escutava as requisições em uma porta e as tratava conforme eu especificava: (Exemplo com node e express)

const server = require("express")();

server.get("/home", function (request, response) {
response.send("Oi meu chapa.");
});

server.listen(8080);

É um processo bem direto, se o cliente mandar uma requisição do tipo GET na rota /home pro meu servidor http://127.0.0.1:8080, ele responde com “Oi meu chapa”.

Eu sabia que estava perdendo alguma coisa importante nesse processo. Foi então, meus amigos, que decidi criar uma API em C — jamais me perdoarei por este erro. Mesmo que a experiência tenha sido dolorosa — e eu fiz tudo acompanhando um tutorial -, consegui me aprofundar um pouco mais no assunto, o que foi bastante interessante.

HTTP

O protocolo HTTP é relativamente simples de entender. Você tem dois computadores: um cliente e um servidor. O cliente envia uma requisição com algumas informações sobre a comunicação no "cabeçalho" e dados extras no "corpo":

POST /home HTTP/1.1
Host: 127.0.0.1:8080
Accept: text/html, */*
Accept-Language: pt-BR,pt;q=0.6
Content-Lenght: 29

Corpo com dados e mais dados.

A primeira linha é a "Request Line", ela contém as informações básicas da comunicação, como a ação que queremos executar, a rota que queremos acessar no servidor e a versão do protocolo. Abaixo dela, temos os cabeçalhos com informações adicionais sobre a requisição e sobre o cliente. Por fim, temos o corpo da requisição que é onde colocamos dados adicionais a serem enviados para o servidor. O que separa o cabeçalho do corpo é uma linha em branco, pra ser mais específico um \r\n.

O servidor recebe a nossa mensagem e responde como foi programado com uma mensagem quase que no mesmo formato:

HTTP/1.1 200 OK
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Fri, 07 Jul 2023 00:53:29 GMT
Server: ECS (mic/9A9C)
Content-Length: 13

Oi meu chapa.

A gente continua tendo os cabeçalhos e o corpo para a mensagem de resposta, mas a primeira linha é a "Response Line". Depois disso a conexão é fechada (depende da versão do protocolo que a gente tá usando, mas releva isso).

O WhatsApp

Até o momento tudo parece tranquilo, mas bora lá. O protocolo é simplesmente uma padronização na comunicação, tipo como acontece no e-mail, ninguém escreve um e-mail como se estivesse escrevendo uma mensagem no Whats (pelo menos eu espero), o meio que possibilita essa comunicação é o aplicativo de mensagens ou e-mail, no caso do HTTP, o meio pra essa troca de mensagens é uma conexão TCP (aí já entra uma pilha de protocolos, HTTP é o topo do iceberg, mas a gente não vai falar sobre isso aqui).

O que facilita, mas ao mesmo tempo dificulta aqui, é que cada um só tem uma oportunidade de falar. Não sei se o Whats tem limite de caracteres, mas em uma conexão TCP a gente não pode enviar 10 gigabytes em uma porrada só, nossa mensagem é quebrada em pequenas partes que são recebidas pelo servidor (pequenos pacotes).

Você pode não notar de início — demorou pra eu notar -, mas isso é meio complicado. Quando o cliente terminar de enviar as mensagens a gente junta tudo como se fosse uma grande mensagem, a gente tem que saber separar as informações. O mais estranho é que o servidor tem que saber quando o cliente terminou de enviar a mensagem (imagina ter que adivinhar quando alguém vai parar de falar). A mensagem inteira é formada por pequenos pacotes de dados, a conexão nos permite enviar uma mensagem a qualquer hora, mas o servidor tem que esperar o cliente terminar de falar para poder começar a enviar a resposta.

Até que não é difícil se a gente seguir as especificações do protocolo, só é trabalhoso. O cabeçalho e a request line tem tudo o que a gente precisa saber, e sabemos que estes estão delimitados pelo início da mensagem até a primeira sequência \r\n (segundo o RFC), após isso a gente tem o corpo com dados quaisquer do cliente (se a requisição houver um corpo), o tamanho dele podemos pegar do cabeçalho Content-Length.

Pois é, se tratando de protocolos na camada de aplicação, é tudo um grande WhatsApp com regras de escrita, pelo menos pro HTTP, e pra WebSockets também (quando eu parar pra estudar o resto escrevo outro artigo).

WhatsApp na prática

Até agora foi só teoria, mas chegou a hora de mostrar como realmente entendi isso — não doido, sem API em C aqui, apenas JavaScript. A gente vai criar um servidor que atende a conexões TCP em uma porta específica e um cliente HTTP para enviar uma requisição para esse servidor. Não vamos implementar as especificações do HTTP para o servidor, a gente só quer ver a mensagem do cliente.

A partir daqui se quiser acompanhar o código é necessário uma versão do node superior a 16.x instalada.

Servidor

Para criar o servidor a gente vai usar o módulo net que é nativo do node. Ele fornece uma interface simples para que gente consiga lidar com conexões TCP. Tudo o que a gente precisa fazer é criar um servidor para atender a conexões em uma porta específica.

/* arquivo: server.mjs */

import net from "node:net";

const server = net.createServer(function connectionHandler(socket) {
socket.on("data", function (data) {
// Imprimindo os dados no terminal
console.log(data.toString("utf8"));
});
});

// Definido a porta onde o servidor vai escutar
server.listen(8080);

Criamos um servidor com uma função handler pra lidar com os sockets TCP das nossas conexões. Em cada conexão, temos um "socket" específico, e por meio deste podemos enviar e receber dados. Como quase tudo em Node.js é orientado a eventos, a gente pode receber os dados ouvindo o evento do tipo "data" no socket, após o evento ser disparado, a função callback que é executada recebe um parâmetro que chamamos de data, nesse parâmetro a gente vai receber um Buffer (estrutura de dados usada pra trabalhar com dados binários) contendo os dados da mensagem do cliente. No código eu uso o método toString pra converter os dados binários em strings.

Com isso a gente já deve conseguir receber nossas mensagens.

HTTP Client

Tô usando a versão 20.3.0 do node, então tenho acesso a fetch API, e por questões de facilidade, é essa que eu vou usar para criar o meu cliente:

/* arquivo: clientHTTP.mjs */

// Criando os dados do corpo da requisição (body)
const body = JSON.stringify({
message: "Iae servidor.",
date: Date.now()
});

// Fazendo a requisição ao servidor
fetch("http://127.0.0.1:8080", {
method: "post",
body: body,
headers: { "Content-Type": "application/json" }
})
// Código pra não dar erro
.then(console.log)
.catch(console.log)

Testando

Agora que temos todo o código pronto é só iniciar o servidor e mandar a mensagem com o cliente.

Cara… que lindo… E veio tudo em uma mensagem só.

Conclusão

Pois é, lidar com computadores pode ser uma loucura, mas isso não deveria ser surpresa, a final, no fundo são tudo zeros e uns, somos nós que criamos as regras, representações e damos significado a tudo isso.

Espero que você tenha se divertido acompanhando essa loucura, e se tiver aprendido algo novo, melhor ainda. Criei o “Diário Backend” justamente para compartilhar aprendizados e opniões de maneira descontraída, se tiver curtido segue a publication, minha página do medium e da um “clap” aqui. Comentários construtivos são sempre bem vindos, no mais, valeu aí meu nobre e até a próxima 🤠

--

--

Afonso Lucas
Diário Backend

 iOS Developer & Backend enthusiast - World is a giant software. Talk to me through port 443 🤠