Introdução
Seja bem-vindo a Programação B, um livro a ser usado na disciplina da UFS de mesmo nome. Essa disciplina busca ensinar ao alunos os principais conceitos e técnicas utilizados no desenvolvimento de aplicações, em particular aplicações web e mobile.
A maioria das pessoas que possuem alguma formação em computação acaba trabalhando na área de desenvolvimento. É, de longe, a sub-área de computação que mais emprega pessoas.
A razão é simples: existe uma variedade imensa de problemas, que interessam a um ou mais clientes. Um cliente pode estar interessado numa solução para a área de veterinária, por exemplo. Outro pode estar interessado numa solução para a área de infraestrutura rodoviária. São assuntos completamente diferentes. Não é possível que exista uma única aplicação que satisfaça a todos os clientes. Portanto, múltiplas aplicações acabam surgindo, adaptadas aos requisitos de um certo grupo de clientes. Devido a essa variedade, existe uma demanda muito grande por profissionais que possam construir tais aplicações. Nosso objetivo é aprender como construir tais aplicações.
Do ponto de vista do currículo de computação, a utilidade dessa disciplina pode ser percebida ao olhar para as disciplinas de POC (Prática Orientada a Computação), que consistem em atender a demanda de um cliente real através da criação de um produto real, com algum grau de inovação. A maioria dos clientes em POC costumam ser profissionais de uma área fora da computação, e a maioria das soluções tendem a tomar a forma de uma aplicação web e/ou mobile. Alguns alunos chegam à disciplina de POC sem conheciemnt dos conceitos e técnicas de desenvolvimento de aplicações, e sem experiência prática nas ferramentas que ajudam nesse processo, tendo que obtê-los “na marra”, durante a disciplina, ao mesmo tempo que lida com o aprendizado de outros conceitos de produto, inovação e gerenciamento de projeto.
Com a ascensão da IA generativa, muitos acreditam que não faz mais sentido estudar conceitos de desenvolvimento, pois a IA pode fazer o desenvolvimento em nome da pessoa. O aluno que tem essa visão deve reconsiderar: a IA, apesar de facilitar radicalmente a codificação e a interpretação de volumes significativos de código, não é uma bala de prata. A qualidade do código gerado por IA é diretamente proporcional ao conhecimento que o usuário tem do domínio do problema. A resolução de problemas específicos e/ou que exigem algum conhecimento do produto ainda precisam de muito esforço por parte do usuário para que a IA entregue um código de alta qualidade, no nível do desejado pelo usuário. De maneira geral, o usuário que pede para a IA fazer X ou Y sem entender ao certo como X ou Y funcionam por baixo está condenado ao fracasso.
Finalmente, espero que o aluno encare este livro não como um manual de receitas a serem decoradas, mas como um convite a entender por que cada técnica existe. Ao longo do curso, nenhum conceito é apresentado antes de a aplicação que construímos sentir a falta dele. Quando você entende o problema que uma técnica resolve, passa a saber não só como usá-la, mas quando não usá-lá. Tal discernimento é essencial.
O que este livro assume
Este livro assume que será lido de maneira sequencial. Capítulos posteriores são construídos em cimas de capítulos anteriores.
Este livro assume que o leitor tem conhecimento intermediário em programação. Este não é um livro de introdução à programação. O leitor deve estar familiarizado com conceitos de programação no geral, e de conceitos dos principais paradigmas de programação (imperativo, funcional e orientado a objetos).
Esse livro assume que o desenvolvimento será feito em um ambiente Linux ou similar (macOS, WSL, etc). Conceitos de linha de comando (terminal) serão introduzidos e utilizados ao longo do livro, porém sem muito detalhamento. Portanto, é desejável que o usuário tenha alguma familiariedade com CLIs. Caso sinta alguma dificuldade para entender os trechos de código a serem digitados no terminal, recomendo que acesse o Command line crash course da MDN.
Além disso, o livro assume que o usuário sabe ler a documentação técnica de linguagens, bibliotecas, etc. Esse tipo de documentação, inclusive, costuma existir somente em inglês.
Como usar este livro
Este livro busca ensinar os conceitos e técnicas de desenvolvimento de aplicações de maneira genérica e agnóstica. Ou seja, queremos que o conhecimento seja transferível para qualquer tipo de aplicação, linguagem ou framework que o aluno venha a usar. Isso não significa que não usaremos uma linguagem ou framework(s) específicos ao longo do curso. Usaremos principalmente TypeScript/JavaScript e os frameworks dessa linguagem. No entanto, o usuário está livre para implementar os exemplos e exercícios em qualquer linguagem (Python, C#, Kotlin, Swift, etc) ou framework (FastAPI, .NET, Spring, Angular, Svelte, etc) que preferir.
O aprendizado gira em torno de uma aplicação de exemplo, o TamoJunto, uma plataforma comunitária de eventos que construímos de forma incremental, capítulo a capítulo. Em vez de montar todo o backend, depois todo o frontend web e por fim todo o mobile, avançamos por fatias verticais: cada nova funcionalidade atravessa o banco de dados, o servidor e as interfaces web e mobile de uma só vez. É assim que aplicações reais são construídas, e é assim que aprenderemos a construí-las.
Introdução ao Desenvolvimento de Aplicações
Tudo nasce com um problema, uma necessidade de alguém. Pode ser algo essencial, como um problema na área de saúde, ou não, como a necessidade de ouvir músicas.
Então, os desenvolvedores devem construir aplicações que resolvam esses problemas e satisfaçam essas necessidades. A área da computação que se preocupa em entender os conceitos e técnicas relevantes na construção dessas aplicações é conhecida como Desenvolvimento de Aplicações. Esse capítulo busca dar um visão geral sobre essa área.
O que é uma aplicação?
Uma aplicação é um software (um programa, ou conjunto de programas) criado para executar tarefas para um usuário, ao contrário de softwares que existem para gerenciar o próprio computador (como um sistema operacional, um driver de dispositivo ou um compilador). A palavra “aplicação” deriva justamente dessa ideia: a aplicação do poder computacional a um problema humano.
Certas propriedades diferenciam uma aplicação de outros tipos de software:
-
Orientado a domínio. Uma aplicação serve para resolve um problema em algum domínio: gerenciar as finanças, editar fotos, reservar passagens aéreas, conversar com os amigos.
-
User-facing. Uma aplicação é uma fronteira de interação. Um usuário humano abre, utiliza e fecha uma aplicação. É o que distingue uma aplicação de uma biblioteca ou serviço de software. Uma biblioteca pode ser usada por uma aplicação; um serviço atua como o motor por trás de uma aplicação; mas a aplicação é a “coisa” que o usuário percebe e interage.
-
Composição. Aplicações modernas raramente são compostas de um único programa. Invés disso, elas são um conjunto de programas interagindo entre si: um site na web, um aplicativo em um dispositivo móvel, um servidor por trás, um banco de dados, etc. Todos esses formam, em conjunto, a aplicação.
Esses programas comunicam-se entre si utilizando protocolos de redes de computadores. O protocolo mais relevante em aplicações modernas é o protocolo HTTP, que será abordado em Noções de Redes de Computadores.
Figura 1-1: Os vários programas que compõem uma aplicação moderna, conectados via protocolos de rede.
Interfaces de Usuário
Como aplicações são user-facing, é necessário que exista alguma forma do usuário-fim emitir comandos para a aplicação. Isto é, é necessário que exista alguma interface de usuário (User Interface ou UI). Uma interface de usuário pode ser implementada de várias formas.
Graphical User Interfaces (GUIs)
A forma mais comum de implementar uma interface de usuário é através de menus, telas e cliques em botões.
A maioria das aplicações que são utilizadas pelo usuário comum, aquele com pouca formação em computação, são classificadas como GUIs (Graphical User Interfaces). Isto é, são aplicações cuja a interface é gráfica, composta de menus, telas, cores, botões, etc. Essas GUIs podem ser acessadas a partir de vários meios ou plataformas. As principais plataformas hoje em dia são:
-
Desktop. São aplicações que são abertas diretamente num computador desktop (PC, laptop) , por meio de algum sistema operacional, como o Windows, Linux ou macOS.
Exemplos: Spotify Desktop, Whatsapp Desktop, Google Chrome, Microsoft Edge, Explorador de Arquivos, Gerenciador de Tarefas, Steam Desktop, etc.
-
Mobile. São aplicações muito semelhantes às desktop mas que, ao invés de serem abertas em um dispositivo desktop, são abertas em dispositivos móveis, como smartphones e tablets, que rodam sistemas operacionais móveis, como Android e iOS.
Exemplos: aplicativo do Spotify no Android, Whatsapp no Android, Whatsapp no iOS, etc.
Aplicações para desktop ou mobile são conhecidas como aplicações nativas, pois rodam diretamente acima do sistema operacional.
-
Web. São aplicações acessadas por meio de um site, utilizando um navegador web (Google Chrome, Mozilla Firefox, Microsoft Edge, Safari).
Exemplos: Spotify Web, Whatsapp Web, SIGAA, etc.
Diferentemente das aplicações nativas, as aplicações web não rodam diretamente acima do sistema operacional, mas sim por meio de um navegador web. Note que quem atua como aplicação nativa nesses casos é o o navegador web.
Figura 1-2: As três plataformas de aplicações gráficas: desktop (nativa), mobile (nativa) e web (via navegador).
Command-Line Interfaces (CLIs)
Enquanto as GUIs são projetadas para o usuário comum, com pouco conhecimento em computação, existem aplicações que são projetadas para usuários com um conhecimento maior em computação. Essas aplicações, ao invés de utilizarem menus, telas e botões, utilizam comandos de texto, digitados em um terminal.
O principal benefício de aplicações CLIs é que, diferentemente das GUIs, elas não escondem as funcionalidades por trás de vários cliques em menus, telas e botões. Dessa forma, é muito fácil de automatizar a interação com aplicações CLI.
Como exemplos dessas aplicações, temos ffmpeg, diskutil, diskpart, ssh,
zip, unzip, rsync, brew, winget, grep, find, ping, entre outros.
Por exemplo, veja como um usuário pode usar a CLI do ffmpeg para converter um
vídeo no formato .mkv para .mp4:
ffmpeg -i video.mkv video.mp4
Figura 1-3: Comparação entre GUI e CLI para a mesma tarefa: converter um vídeo com ffmpeg.
Apesar de CLIs serem muito úteis, o foco desse livro é em aplicações gráficas (GUIs), em particular nas plataformas web e mobile.
Componentes da Aplicação
Aplicações são compostas de vários programas que comunicam-se entre si. Esses programas/componentes recebem nomes específicos a depender do papel que eles desempenham na aplicação como um todo.
-
O frontend (também chamado de cliente ou client-side) é o componente da aplicação que implementa a interface que o usuário interage. O frontend pode ser implementado como um site web (frontend web), um aplicativo de dispositivo móvel (frontend mobile), um aplicativo de dispositivo desktop (frontend desktop) ou uma CLI.
O código do frontend é executado no dispositivo de cada cliente/usuário da aplicação. Se uma aplicação possui 10 usuários simultâneos, o código do frontend está sendo executado em 10 dispositivos ao mesmo tempo.
-
O backend (também chamado de servidor ou server-side) é o componente da aplicação que implementa as regras de negócio por trás da interface. Isso é feito através da interação com o banco de dados, validação de regras específicas do domínio (ex: um pedido não pode exceder R$ 5.000 sem aprovação do gerente), controles de segurança e integrações com outros serviços.
O backend geralmente é implementado como uma API (Application Programming Interface), uma interface programática (isto é, acessada por meio de código e/ou protocolos de redes) que é acessada pelo frontend dos vários clientes.
O código do backend, diferente do frontend, não é executado no dispositivo de cada cliente, mas sim em um ou mais servidores. Um servidor é um computador dedicado à execução de serviços e aplicações, configurado pela pessoa ou organização que oferece a aplicação.
-
O banco de dados (também chamado de camada de persistência) é o componente da aplicação que armazena os dados da aplicação e é responsável pela execução de consultas e outros comandos sobre esses dados.
Diferentemente do frontend e do backend, o banco de dados é um software que não costuma ser implementado pelo desenvolvedor da aplicação. Invés disso, o desenvolvedor utiliza um banco de dados já implementado.
Existem bancos de dados relacionais (baseados em tabelas de registros com esquema fixo) e não-relacionais (baseados em vários outros modelos, como documentos flexíveis ou grafos). Alguns exemplos de bancos de dados: Postgres, MariaDB, MySQL, Oracle, MS-SQL, MongoDB, Neo4j, ScyllaDB, etc.
Figura 1-4: O Spotify como exemplo de aplicação em 3 camadas: múltiplos frontends, um backend com API, e um banco de dados.
A maioria das aplicações modernas possuem pelo menos esses 3 componentes. Esse tipo de aplicação é conhecida como aplicação em 3 camadas.
Aplicações mais antigas não possuíam uma distinção rígida entre as camadas de frontend e backend. Ao invés disso, o programa que implementava a interface de usuário (quase sempre uma aplicação desktop) interagia diretamente com o banco de dados, formando assim uma aplicação em duas camadas.
Aplicações mais complexas geralmente dividem seu backend em microserviços, cada um funcionando como um backend isolado que oferece uma API. Elas também envolvem outros componentes, como filas, tópicos, brokers, caches, balanceadores de carga, proxy reverso, API gateways, service meshes, engines de busca, object storage, schedulers/job runners, service discovery e observabilidade.
Capítulo 2: Noções de Redes de Computadores
Hoje em dia, a base de quase qualquer aplicação que você imaginar, seja ela web, mobile ou desktop, é o protocolo de rede HTTP (HyperText Transfer Protocol). Compreender ele é essencial para entender como as aplicações que construíremos funcionam por baixo.
Se você não faz ideia do que seja um “protocolo”, não tem problema. Esse capítulo define conceitos fundamentais de redes de computadores, no nível que é necessário para o nosso curso.
Redes de Computadores
Os conceitos que iremos falar aqui podem ser vistos em qualquer curso ou livro de Redes de Computadores. Redes é uma sub-área de computação dedicada a entender como computadores, separados fisicamente por alguma distância, trocam dados entre si. É uma das partes mais importantes de um currículo de computação, pois o mundo moderno inteiro depende dos conceitos estudados nessa área.
Só para te dar uma noção, vamos ver como redes de computadores acabam surgindo no seu dia:
- Seu celular toca o alarme e te acorda.
- Você abre o Whatsapp para ver as mensagens. O seu celular usa conceitos de redes para obter as mensagens que foram enviadas para você. Ele inicia uma comunicação com os computadores (chamados aqui de “servidores”) do Whatsapp/Meta.
- Você pede uma corrida na Uber. O seu dispositivo precisa se comunicar com os servidores da Uber. E os dispositivos dos motoristas também precisa se comunicar com os servidores da Uber. Eles se comunicam usando conceitos de redes.
- Você encontra uma corrida. Tanto o dispositivo do passageiro quanto do motorista enviam continuamente informações para o servidor da Uber, contendo suas respectivas coordenadas. O servidor da Uber lê essas informações e as exibe no aplicativo para os dois usuários envolvidos na corrida: você e o seu motorista.
- Você abre o Youtube ou a Netflix. Seu dispositivo precisa obter os dados que compõem os vídeos (áudio, imagens, legendas, etc) de algum lugar. Para isso, ele se comunica com o servidor do Youtube/Netflix, para então exibir os vídeos no seu dispositivo. Eles se comunicam usando conceitos de redes.
- Você compartilha um arquivo via Bluetooth com um amigo de sala. Para isso funcionar, é preciso que os dois dispositivos se comuniquem entre si, no que chamamos de “rede sem fio”, utilizando apenas ondas eletromagnéticas. Eles se comunicam usando conceitos de redes.
Acho que deu pra ter uma noção da importância dessa disciplina para a sociedade moderna.
Conceitos básicos de redes
A figura abaixo ilustra uma rede de computadores simples, com quatro hosts conectados entre si.
Figura 2-1: Quatro hosts conectados formando uma rede de computadores.
Rede
Uma rede de computadores, ou simplesmente rede, nada mais é do que um conjunto de computadores que, mesmo separados fisicamente, podem trocar informações entre si.
Por “computadores”, eu quero dizer qualquer dispositivo de computação: Desktops, celulares, consoles de videogame, Smart TVs, carros, etc. Muitas vezes, esses computadores que participam da rede são chamados de hosts.
Existem redes privadas, que são gerenciadas e acessadas por uma organização, e redes públicas, que são acessíveis por múltiplas organizações. A rede pública mais importante de todas é a Internet.
A troca de informações numa rede não pode ser feita de qualquer forma. Para dois computadores trocarem informações, é importante que ambos falem a mesma língua e sigam as mesmas regras, o que nos leva ao próximo conceito.
Protocolo
Um conceito que está presente em toda a disciplina de redes é o de protocolo: um conjunto de regras que duas ou mais partes devem seguir fielmente para atingir um objetivo comum. Para um protocolo funcionar é necessário que todas as partes estejam cientes dele e o sigam.
Um protocolo de rede é um protocolo onde as partes são computadores! E as regras dos protocolos de rede geralmente seguem mais ou menos a mesma cara, geralmente fazendo alguma dessas funções:
- Definem quais são os tipos de mensagens possíveis que as partes podem trocar
- Definem qual o conteúdo das mensagens trocadas.
- Como as partes são computadores, esse conteúdo é sempre em binário (1s e
0s). Então uma mensagem possível é
01110110001que pode representar umOLÁ, por exemplo.
- Como as partes são computadores, esse conteúdo é sempre em binário (1s e
0s). Então uma mensagem possível é
- Definem o fluxo válido das mensagens.
- Definem os erros e/ou situações de ajuste, e como proceder.
Em redes, temos vários protocolos, cada qual com um objetivo específico. Por exemplo, alguns protocolos que são usados hoje em dia em várias aplicações são:
- O SMTP (Simple Mail Transfer Protocol), que é o protocolo que permite envio e transmissão de e-mails entre computadores. É o que o Gmail, Outlook, entre outros usam quando precisam enviar um e-mail.
- O BitTorrent, que é o protocolo que permite a transmissão de torrents entre computadores. É o que aplicações como qBittorrent, µTorrent, entre outros usam.
- O DNS (Domain Name System), que é o protocolo que permite que nossos
celulares e computadores consigam traduzir um nome como
sigaa.ufs.br, que só faz sentido para humanos, para algo que possa ser entendido por máquinas.
Os protocolos são definidos em documentos de texto, conhecidos como RFCs (Request for Comments). Por exemplo, o SMTP é definido na RFC 5321, enquanto o DNS é definido na RFC 1035.
Camadas
Os protocolos não são todos iguais. Isso ocorre pois certos protocolos ficam responsáveis por apenas algumas partes da comunicação, enquanto outros ficam responsáveis por outras.
Por exemplo, considere o caso em que o protocolo SMTP seja utilizado pelo servidor do Gmail para enviar um e-mail para o servidor do Outlook.
Não faz sentido que a RFC do SMTP responda as seguintes perguntas:
- Como deve ser feita a transmissão física dos bits entre dois computadores? Será usado fio de cobre, fibra ótica ou tranmissão sem-fio (ondas de rádio). Qual o nível de voltagem que será usado para representa um bit 0 e um bit 1? Ou que deverá ser feito se acontecer alguma interferência eletromagnética na hora de transferir esses sinais elétricos. Essas são preocupações físicas, sem relação direta com troca de emails.
- Como o hardware de um dispositivo deve distinguir entre os sinais elétricos destinados para diferentes conexões num mesmo computador? Quando os sinais elétricos de uma conexão terminam e o de outra começam? Como garantir que os dados sejam direcionados para o dispositivo correto, dentro de uma rede local? Essas são preocupações de enlace, sem relação direta com troca de emails.
- Qual rota de computadores deve ser seguida para um certa conexão? Como diferenciar globalmente dois computadores? Como redes de diferentes organizações/empresas devem se conectar? Essas são preocupações a nível de redes em si, sem relação direta com troca de emails.
- Como as conexões são representadas no sistema operacional de cada dispositivo? Como associar um processo do sistema operacional a uma conexão de rede? Devemos garantir que o processo do computador de destino recebeu a última mensagem? Se sim, como? Como regular a velocidade da transmissão para evitar que a qualidade da rede seja degradada? Essa são preocupações de transporte, sem relação direta com troca de emails.
Nenhuma dessas preocupações dizem respeito a troca de emails, mas todas devem ser respondidas antes que qualquer email seja enviado. Além disso, essas preocupações não devem ser respondidas apenas para a troca de emails, mas também para outros tipos de aplicações, como transferência de arquivos, navegar pela web, DNS, BitTorrent, etc.
De fato, cada uma dessas preocupações se encaixa em uma camada.
Figura 2-2: As 5 camadas do modelo TCP/IP, da mais baixa (física) à mais alta (aplicação).
Uma camada de rede é o conjunto de preocupações e responsabilidades que podem ser resolvidas em conjunto, por um mesmo protocolo.
Elas recebem o nome de camadas pois certas responsabilidades precisam ser resolvidas anteriormente, criando uma hierarquia ou camada de responsabilidades. Um protocolo numa camada \( L \) precisa que um protocolo na camada \( L - 1 \) já tenha resolvido os problemas sob sua responsabilidade. De maneira geral, dizemos que a camada \( L - 1 \) provê serviços para a camada \( L \).
Modelos de Rede: OSI e TCP/IP
Um modelo de rede é uma forma específica de definir quantas e quais são as camadas de rede. Existem dois modelos de rede: o modelo OSI e o modelo TCP/IP.
O modelo OSI é utilizado em alguns contextos acadêmicos e a sua numeração (de 1 a 7) é a que as pessoas geralmente utilizam para se referir as camadas. Já o modelo TCP/IP é o que é de fato implementado na Internet e utilizado na prática.
Abaixo, descrevemos as 5 camadas do modelo TCP/IP, a partir da mais básica (baixo nível):
- Camada Física
- Camada que se preocupa em como será feita a transmissão física dos sinais que carregam os dados na rede. É onde são projetados os padrões dos meios de transmissão, como cabos, ondas de radio-frequência, etc.
- Principais protocolos dessa camada: IEEE 802.3 (redes cabeadas, também conhecido como Ethernet), IEEE 802.11 (redes sem fio do tipo Wi-fi), IEEE 802.15 (redes sem fio do tipo Bluetooth). Importante notar que esses protocolos cobrem ambas a camada física e de enlace, pois essas possuem uma dependência muito grande entre si.
- Camada de Enlace
- Camada que se preocupa no nível lógico imediatamente acima da transmissão física, em particular na conexão ponto-a-ponto (o enlace ou link) entre dois hosts.
- Principais protocolos dessa camada: IEEE 802.3 (redes cabeadas, também conhecido como Ethernet), IEEE 802.11 (redes sem fio do tipo Wi-fi), IEEE 802.15 (redes sem fio do tipo Bluetooth).
- Camada de Rede
- Camada que se preocupa em definir o escopo de uma rede e como será feita a comunicação entre duas redes. Define um endereço lógico para cada host chamado endereço IP (Internet Protocol). É esse endereço que é utilizado para rotear as comunicações entre os hosts.
- Principais protocolos dessa camada: IPv4, IPv6, ICMP, ICMPv6, etc.
- Camada de Transporte
- Camada que se preocupa na comunicação fim-a-fim entre dois processos do sistema operacional de cada host.
- Principais protocolos dessa camada: TCP, UDP, SSL/TLS (baseado no TCP), QUIC (baseado no UDP).
- Camada de Aplicação
- Camada que se preocupa em definir o funcionamento das aplicações de rede, que entram em contato direto com o usuário. Troca de emails, envio de arquivos, impressão e navegação web são exemplos de “aplicações” aqui, pois representam as ações que o usuário-fim quer realizar.
- Principais protocolos dessa camada: DNS, DHCP, HTTP, SMTP, FTP, WebSockets, BitTorrent.
Endereço de Rede/Endereço IP
Dentro de uma rede de computadores, é necessário que um participante dela (um host) saiba como identificar e diferenciar uma conexão que deve ser direcionada para o host A de outra que deve ser direcionada para o host B. Para resolver esse problema, foram criados os endereços de rede.
Cada host numa rede é identificado pelo seu endereço de rede. Um endereço de rede é simplesmente uma sequência de números que pode ser interpretada para identificar hosts.
No modelo TCP/IP, o endereço de rede segue o formato dos protocolos da camada de rede: IPv4 (Internet Protocol version 4) e/ou IPv6 (Internet Protocol version 6). Nesses casos, um nome alternativo para o endereço de rede é “endereço IP” ou apenas “IP”.
Vamos ver um exemplo, seguindo o formato do protocolo IPv4:
- Um host
Atem o endereço de rede10.10.3.47. Ele está se comunicando com dois outros hosts:B(cujo endereço é10.10.3.3) eC(cujo endereço é10.10.5.98). - Sempre que
Aquiser mandar uma mensagem paraB, ele manda um pacote cujo endereço de destino é10.10.3.3.Cnão é o destino dessa mensagem.
Figura 2-3: Host A usa o endereço IP de destino para direcionar pacotes para B ou C.
Note que se um mesmo host está conectado em duas ou mais redes distintas (como um roteador que roteia pacotes entre duas ou mais redes), ele terá mais de um endereço IP.
Existem dois endereços IP especiais, chamados de endereços de loopback, que
podem ser usados por um computador para se referir a ele mesmo. Um é o IPv4
127.0.0.1. O outro é o IPv6 ::1. Esses endereços são úteis na fase de
desenvolvimento, onde executamos uma aplicação na nossa própria máquina local.
Dessa forma, podemos nos comunicar com a aplicação através dos endereços de
loopback, simulando um host externo.
Serviços de Rede
Enquanto na camada de rede os hosts de origem e destino são representados por meio de endereços IP, na camada imediatamente acima, a camada de transporte, um mesmo host pode oferecer vários serviços de rede. Um serviço de rede é um programa cuja entrada é obtida a partir de requisições de outros hosts (chamados de clientes) que chegam por meio de uma rede de computadores.
Ou seja, um serviço de rede é um processo do sistema operacional que tem a capacidade de escutar por requisições que vêm de uma rede de computadores e responder a essas requisições.
Cada serviço de rede executa algum protocolo da camada de aplicação que os dois lados da comunicação, tanto servidor (o host que executa/oferece o serviço de rede) quanto os clientes (os hosts remotos que mandam requisições e solicitam o serviço de rede), precisam saber falar.
Portas
Da mesma forma que podemos ter múltiplos processos em execução no sistema operacional, podemos ter múltiplos serviços de rede em execução. Portanto, o sistema operacional precisa distinguir o tráfego de rede que vai para um serviço de rede \( A \) do tráfego que vai para outro serviço de rede \( B \). Ou seja, o sistema operacional precisa demultiplexar1 o tráfego de rede.
A maneira pela qual o SO faz essa demultiplexação é utilizando portas de rede.
Cada porta de rede é um par (Protocolo de Transporte, Número de Porta) que
identifica a qual serviço de rede um certo tráfego pertence. Nesse par:
-
Protocolo de Transporteé um dos dois protocolos básicos da camada de transporte: TCP (Transmission Control Protocol) ou UDP (User Datagram Protocol).O TCP é um protocolo orientado à conexão, o que significa que ele garante que um lado da comunicação saiba que as mensagens enviadas de fato chegaram ao destino e na ordem correta. Além disso, ele possui alguns controles para garantir a qualidade da rede. No entanto, é um protocolo que possui mais overhead (sobrecarga), isto é, custo computacional.
Já o UDP é um protocolo não-orientado à conexão, o que significa que os participantes da comunicação não tem garantia sobre se as mensagens chegaram na ordem correta ou se elas sequer chegaram. Para compensar, é um protocolo com consideravelmente menos overhead.
-
Número de Portaé um número de 16 bits (vai de 0 a 65535). Esse número serve para permitir que, em um mesmo computador, múltiplos serviços de rede de um mesmo protocolo de transporte possam estar em execução em um dado momento.
Cada serviço de rede em execução num dado host é mapeado para uma porta de rede, e a porta atua como um endpoint de comunicação. Por exemplo, num mesmo computador/host podemos ter 3 serviços de rede em execução:
- O serviço de rede do Spotify para fazer streaming de música usando a porta TCP/4070.
- O serviço de rede para enviar emails (protocolo SMTP) usando a porta TCP/25.
- O serviço de rede para transferir arquivos (protocolo FTP) usando a porta TCP/21.
Figura 2-4: O SO usa portas para direcionar o tráfego ao serviço de rede correto.
Certas portas são chamadas de portas bem-conhecidas (well-known ports), pois geralmente são usadas para os serviços de rede (protocolos) mais comuns. Essas portas usam números que vão de 0 a 1023. Por exemplo, a porta TCP/80 é sempre utilizada para o protocolo HTTP. A porta TCP/443 é sempre utilizada para o protocolo HTTPS. A porta UDP/53 é muito usada para o protocolo DNS. E por aí vai.
Hostname
Apesar dos computadores utilizarem endereços IP, é muito mais fácil para um
humano lembrar de mail.google.com do que lembrar do IP 142.250.219.37.
Portanto, todo host costuma ter um hostname, que é o nome legível do host,
além de um IP. Por exemplo, suponha que uma organização tenha os seguintes
hosts:
- Um host para o seu portal web. O hostname do host é
wwwe o IP é10.100.3.2. - Um host para o seu serviço de e-mail. O hostname do host é
maile o IP é10.100.3.3. - Um host para o seu serviço de API2. O hostname do host é
apie o IP é10.100.3.4.
Domínio
Para agrupar os hostnames de todos os hosts que pertencem a uma mesma organização ou estrutura, é usado um domínio: uma sequência de nomes (legíveis por humanos), cujas partes são separadas por pontos.
Geralmente, a parte final de um domínio é o chamado de TLD (Top-Level Domain), que é comum a várias organizações que compartilham uma mesma característica. Então, por exemplo:
- O domínio
.ufs.bré utilizado pela organização UFS. Seu TLD é.br, geralmente usado por organizações do Brasil. - O domínio
.globo.comé utilizado pela organização Globo. Seu TLD é.com, que vem de company, geralmente usado por organizações empresariais. Note que mesmo sendo uma organização brasileira, o domínio da Globo não está no TLD.br.
FQDN
Sempre que alguém quer se referir a um host específico de uma organização, evitando qualquer ambiguidade, ele utiliza o FQDN (Fully Qualified Domain Name) do host, que nada mais é do que a junção de hostname + domínio.
Figura 2-5: Um FQDN é a junção de hostname + domínio. O TLD é a parte final do domínio.
O FQDN também pode ser usado para obter o endereço IP do host. Alguns exemplos:
- O FQDN
www.google.comrefere-se ao host que oferece o serviço do site do Google. - O FQDN
g1.globo.comrefere-se ao host que oferece o serviço g1 da Globo. - O FQDN
ge.globo.comrefere-se ao host que oferece o serviço ge da Globo.
DNS
Lembre-se que os computadores não trabalham com hostnames, domínios ou FQDNs,
mas sim com endereços IP. Portanto, sempre que digitamos algo como
www.google.com, o computador precisa traduzir esse FQDN para um endereço IP.
Essa tradução (ou resolução) de nomes só é possível graças ao protocolo DNS
(Domain Name System). Sempre que digitamos www.google.com, o DNS entra em
ação e traduz esse FQDN para algum endereço IP (exemplo: 142.250.219.34).
Figura 2-6: O cliente consulta o DNS para obter o IP e então conecta-se ao servidor.
O Protocolo HTTP
Vimos que aplicações são compostas de vários componentes que se comunicam entre si: frontend (web e/ou mobile), backend, banco de dados, etc. Apesar de não ser uma regra, hoje em dia praticamente toda aplicação realiza a comunicação entre frontend e backend utilizando um protocolo da camada de aplicação chamado HTTP (HyperText Transfer Protocol).
Modelo Cliente-Servidor
Sempre que temos uma conversa que segue o protocolo HTTP, são definidos dois papéis essenciais:
- O servidor HTTP: É um host que fica sempre escutando por novas conexões.
Ele recebe requisições dos clientes e responde elas. Por exemplo, imagine que
você acessa o SIGAA em http://sigaa.ufs.br/. Nesse
caso, o servidor HTTP é o próprio
sigaa.ufs.br(este é o FQDN do host que executa, ou hospeda, o SIGAA). - O cliente HTTP: É o host que inicia novas conexões. É ele quem manda as requisições para o servidor e fica aguardando pela resposta dele. No exemplo anterior, quando você acessa o SIGAA em http://sigaa.ufs.br/, você é o cliente HTTP. Note que é possível que dois ou mais clientes HTTP diferentes conectem-se ao mesmo servidor HTTP.
Ou seja, o protocolo HTTP segue o chamado Modelo Cliente-Servidor. A imensa maioria dos protocolos da camada de aplicação seguem esse modelo3.
Figura 2-7: O cliente inicia a conexão e envia requisições; o servidor aguarda e responde.
Mensagens HTTP: Requisições e Respostas
Toda conversa entre o cliente e o servidor é uma sequência de mensagens HTTP. Uma mensagem HTTP pode ser de dois tipos: uma Requisição HTTP (enviada pelo cliente para o servidor) ou uma Resposta HTTP (enviada pelo servidor para o cliente).
Figura 2-8: Ciclo HTTP: o cliente envia uma requisição e o servidor retorna uma resposta.
Anatomia de uma Requisição HTTP
Toda comunicação HTTP começa por uma requisição, pois é o cliente quem inicia a comunicação. O servidor fica passivamente aguardando novas conexões iniciadas pelos clientes.
Vamos primeiro ver um exemplo de requisição HTTP4. A requisição abaixo é de um cliente que está acessando uma página web sobre um relatório do clima.
Figura 2-9: Partes de uma requisição HTTP: request line (método, path, query, versão), headers e payload.
GET /blog/2026/08/relatorio-clima.html?page=2&sort=date&highlight=temperatura HTTP/1.1
Host: www.guardian.co.uk
Accept: text/html
Accept-Language: pt-BR
User-Agent: Chrome/127.0.0.0
Cookie: session_id=a3f8b2c1e9d04716; _ga=GA1.2.1847263950.1723641200; consent=accepted
Connection: keep-alive
Vamos entender parte por parte:
-
A primeira linha é a linha de requisição (request line). Ela contém 4 partes:
- Um método (
GET, especificando que quer obter/ver algum conteúdo). - Um path (
/blog/2026/08/relatorio-clima.html, especificando a localização do conteúdo que será visto). - Uma query string (
?page=2&sort=date&highlight=temperatura, especificando parâmetros para mudar/personalizar o conteúdo retornado). - A versão do protocolo HTTP (
HTTP/1.1, especificando quais funcionalidades HTTP estão disponíveis).
- Um método (
-
As próximas 6 linhas são cabeçalhos (headers) HTTP. Cada cabeçalho tem um nome (ex:
Host) e um valor (ex:www.guardian.co.uk) separados por:.Os cabeçalhos HTTP permitem configurar a conexão e manter valores (estado) ao longo dela.
- O header
Host: www.guardian.co.ukespecifica o host sendo acessado5. Accept: text/htmlespecifica qual o tipo esperado do conteúdo. Nesse caso, é esperado um conteúdo de texto que forma um documento HTML. Veremos mais a frente o que é um documento HTML.Accept-Language: pt-BRespecifica que o conteúdo deve ser retornado preferencialmente em Português do Brasil.User-Agent: Chrome/127.0.0.0indica que o cliente acessou o servidor por meio do navegador Google Chrome na versão127.0.0.0. O valor desse header muda a depender do programa usado para acessar o site, podendo ser desde navegadores até ferramentas CLI comocurl.Cookie: session_id=a3f8...; _ga=GA1.2...; consent=acceptedé o header que armazena os famosos cookies do site. Esse header guarda 3 cookies:session_idé o ID de uma sessão específica do usuário naquele site. É por meio desse cookie que é possível fechar um site e, ao reabrí-lo, ainda estarmos conectados na nossa conta._gaé um cookie usado pelo Google Analytics, um serviço da Google que os sites contratam para poder rastrear e analisar o tráfego dos usuários, coletando dados como páginas visualizadas, duração da sessão, localização geográfica, tipo de dispositivo, etc.consenté um cookie usado para identificar se um usuário consentiu com os termos de uso do site, como os cookies que são utilizados para rastrear o comportamento do usuário. É relevante principalmente em países que possuem legislação sobre uso de dados, como a LGPD.
Connection: keep-aliveespecifica que o clienter quer re-utilizar a mesma conexão TCP, re-aproveitando as configurações já feitas.
- O header
-
Após os headers pode vir uma ou mais linhas que formam o payload (ou corpo) da requisição. Como trata-se de uma requisição
GET, não há necessidade de payload.
Método
Cada requisição HTTP possui um método (também chamado de verbo). O método HTTP representa que tipo de ação aquela requisição pretende realizar. Os 5 principais métodos HTTP são:
GET: Representa a ação de exibir um conteúdo ou listar os conteúdos existentes.POST: Representa a ação de criar um conteúdo.PUT: Representa a ação de substituir/modificar um conteúdo por completo.PATCH: Representa a ação de substituir/modificar partes de um conteúdo.DELETE: Representa a ação de remover um conteúdo.
As requisições que usam os métodos POST, PUT e PATCH costumam possuir uma
payload (corpo da requisição), contendo os dados sobre o que será
criado/modificado.
Veremos mais a frente como o padrão REST utiliza esses métodos para representar o gerenciamento de recursos.
Path
Uma requisição HTTP sempre diz respeito a algum conteúdo ou recurso. O path (caminho) da requisição indica o caminho daquele conteúdo/recurso em específico.
Por exemplo:
- O path
/é geralmente usado para representar a raiz daquele site, e geralmente retorna a página inicial do site. - O path
/blogpode ser usado para representar todas as postagens do blog de um determinado site. - O path
/blog/2026pode ser usado para representar todas as postagens do blog do ano de 2026. - O path
/blog/2026/08/relatorio-clima.htmlpode ser usado para representar uma postagem em específico do blog. Note que o path termina em.html, indicando que esse recurso trata-se de um arquivo/documento HTML. - O path
/blog/imagens/banner.pngrepresenta uma imagem em específico do blog. Note que o path termina em.png, indicado que esse recurso trata-se de uma imagem PNG. - O path
/api/usuarios/42representa o recurso do tipo usuário cujo ID é igual a 42.
Query String
Dado um path, é possível que queiramos customizar o conteúdo retornado. Essas customizações normalmente são especificadas na parte da requisição chamada query string (também chamada de query parameters).
Geralmente, essas customizações costumam ser:
- Qual filtro de busca utilizar
- Qual página dos resultados deve ser retornada
- Se os resultados devem ser ordenados, crescentemente ou decrescentemente.
A query string é uma lista de pares parâmetro=valor, separador por &. A
lista de query parameters sempre começa com ?. Por exemplo, veja a query
string do exemplo, ?page=2&sort=date&highlight=temperatura. Temos 3
parâmetros:
page=2especifica que deve ser retornada a segunda página de resultados.sort=dateespecifica que os resultados devem vir ordenados pela data.highlight=temperaturaespecifica que o campotemperaturadeve ter destaque.
Nem toda requisição tem uma query string. Ela é opcional.
A junção do path + query params formam o alvo da requisição.
Um dos benefícios dos query params é que como eles fazem parte do alvo da
requisição, eles podem ser salvos e uma dada consulta (com seus filtros e
opções) pode ser salvo e reutilizado. Por exemplo, o seguinte pode ser colado na
barra de navegação para levar a uma consulta do Google pelo termo ufs:
https://www.google.com/search?q=ufs. Veja
que é utilizado o path /search e a query string ?q=ufs.
Versão HTTP
Toda requisição HTTP inclui a versão do protocolo que será utilizada. Atualmente, existem 4 versões em uso:
HTTP/1.0: Versão definida na RFC 1945. Estabeleceu os fundamentos do protocolo. Alguns servidores antigos ainda utilizam essa versão.HTTP/1.1: Definida na RFC 2616. Estabeleceu otimizações importantes, como conexões persistentes (Connection: keep-alive). É a versão mais comum de ser vista na Internet.HTTP/2: Protocolo deixou de ser baseado em texto e se transformou num protocolo binário. Funcionalidades: Multiplexação de várias mensagens sobre um mesmo canal de comunicação, compressão de cabeçalhos, priorização de arquivos, server push, etc.HTTP/3: Versão mais recente, ainda pouco utilizada. Funcionalidades: troca do protocolo TCP pelo protocolo QUIC, criptografia obrigatória, mais rápido, etc.
Apesar disso, você só lerá requisições mencionando as duas primeiras versões,
pois elas são baseadas em texto, já o HTTP/2 e o HTTP/3 são protocolos
binários (não são legíveis por humanos).
Headers
Os campos anteriores juntos formam a linha de requisição (request line) de uma requisição HTTP. A request line é a primeira linha da requisição. Alguns exemplos de request lines:
GET /index.html HTTP/1.1GET /search?q=openai&lang=en HTTP/1.1POST /api/usuarios HTTP/1.1
Além da request line, uma requisição HTTP também possui os cabeçalhos
(headers). Os headers são pares Nome: Valor e servem para adicionar
metadados e configurações para a comunicação em si.
Alguns exemplos de headers relevantes:
-
Host. Especifica qual o FQDN do host que atua como servidor HTTP5. Esse é o único header de requisição que é obrigatório.Exemplo:
Host: www.exemplo.com. -
Cookie. É o header cujo valor são os famosos cookies do site. Esses cookies são utilizados para guardar informações que persistem ao longo de várias requisições HTTP (estado). Cada cookie é um parnome=valor, separados por;.Exemplo:
Cookie: session_id=abc123def456; theme=dark; lang=pt-BR, com 3 cookies. -
Referer. É o header utilizado quando um site te redireciona para outro, e o seu valor é a URL do site que solicitou o redirecionamento.Exemplo:
Referer: https://www.example.com/products?category=shoes.
Os headers vêm após a request line. Cada header é escrito em uma linha.
Payload
Quando a requisição HTTP usa métodos como POST, PUT e PATCH, ela inclui
um payload (também chamado de corpo da requisição).
Por exemplo, considere a seguinte requisição, que cria uma nova usuária em um sistema:
POST /api/users HTTP/1.1
Host: example.com
Content-Type: application/json
Content-Length: 68
{"name":"Alice Morrow","email":"alice@example.com","age":29,"active":true}
Vamos entender por partes:
-
A request line é
POST /api/users HTTP/1.1. Portanto, ela irá incluir um payload. Após a request line, seguem 3 headers.Host: example.comé o host que atua como servidor HTTP.Content-Type: application/jsoné um header que especifica qual o tipo do conteúdo que está no payload. Nesse caso,application/jsonindica que o payload segue o formato JSON, que veremos mais a frente.Content-Length: 68é um header que especifica o tamanho do payload em bytes.
-
Após os 3 headers, temos uma linha em branco, que serve para indicar que a lista de headers acabou e o payload virá em seguida.
-
O conteúdo do payload é:
{"name":"Alice Morrow","email":"alice@example.com","age":29,"active":true}Os dados desse payload contêm o nome, email, idade e o status de ativação da nova usuária.
Anatomia de uma Resposta HTTP
Toda requisição HTTP precisa ser respondida pelo servidor. Uma resposta HTTP tem uma estrutura mais simples que as requisições.
Suponha que fizemos uma requisição da forma GET /index.html HTTP/1.1. É uma
requisição para a página inicial de algum site. A resposta que o servidor HTTP
entregou foi:
HTTP/1.1 200 OK
Date: Sun, 16 Aug 2026 18:37:12 GMT
Server: Nginx/1.25.2
Content-Type: text/html; charset=utf-8
Content-Length: 171
Connection: keep-alive
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Página Inicial</title>
</head>
<body>
<h1>Seja bem-vindo!</h1>
<p>A página carregou com sucesso.</p>
</body>
</html>
Vamos analisar por partes:
- A primeira linha é a linha de resposta (response line), contendo:
- A versão do protocolo (
HTTP/1.1) - O status code (
200) - A reason phrase (
OK), uma texto legível explicando o status code. Um status code sempre terá a mesma reason phrase.200 OK= sucesso.
- A versão do protocolo (
- Segue-se uma linha para cada cabeçalho de resposta (response header).
Perceba que alguns headers também podem aparecer em requisições:
Date: Sun, 16 Aug 2026 18:37:12 GMTindica o horário que o servidor mandou a resposta.Server: Nginx/1.25.2indica qual o programa ou biblioteca utilizado pelo servidor para enviar respostas HTTP.Content-Type: text/html; charset=utf-8indica que o corpo da resposta vem no formato HTML e foi codificado utilizando o padrão UTF-8.Content-Lenght: 171indica que o corpo da resposta tem 171 bytes.Connection: keep-aliveindica que deve ser re-utilizada a mesma conexão TCP.
- Uma linha em branco indica o fim da lista de headers e o início do corpo da resposta.
- Seguem 171 bytes que formam o corpo da resposta. A resposta é um document no formato HTML que será exibido para o usuário, montando a estrutura e o conteúdo da página inicial.
Status Code e Reason Phrase
O servidor irá indicar o tipo de resultado usando o status code, que é um código de 3 dígitos. O status code pode indicar se foi falha, falha por erro do cliente, falha por erro do servidor, redirecionamento, timeout, etc).
Todo status code está associado a um texto legível chamado reason phrase, dando mais detalhes do que aconteceu para além do código numérico.
Os status codes podem ser classificados em categorias de acordo com o primeiro dígito:
| Categoria | Tipo | Exemplos |
|---|---|---|
1xx | Informação | 100 Continue 101 Switching Protocols |
2xx | Sucesso | 200 OK 201 Created |
3xx | Redirecionamento | 301 Moved Permanently 302 Found |
4xx | Erro do Cliente | 400 Bad Request 403 Forbidden 404 Not Found |
5xx | Erro do Servidor | 500 Internal Error 502 Bad Gateway 503 Service Unavailable |
Você pode consultar todos os status codes, seus significados e dicas de como usá-los na referência da MDN: Códigos de status de respostas HTTP.
Headers
Assim como as requisições, as respostas HTTP possuem headers para carregar metadados e configurar a conexão.
Alguns headers são exclusivos das respostas, como Server. Já outros podem ser
usados tanto por requisições como por respostas, como Connection,
Content-Type e Content-Length.
Body/Corpo
A parte mais importante de uma resposta HTTP é o corpo (body). É nele que está o conteúdo que o cliente está interessado. Esse conteúdo geralmente é:
- Um documento HTML, que define a estrutura e o conteúdo de uma página web.
- Uma arquivo de mídia (imagem, áudio, vídeo)
- Dados no formato XML ou JSON
O corpo da resposta pode ser vazio somente em algumas ocasiões, como em resposta
a uma requisição DELETE.
E o HTTPS?
Praticamente todos os sites que você acessa hoje na web começam com https://.
Esses sites usam o protocolo HTTP junto com um protocolo de segurança chamado
SSL/TLS (Secure Socket Layer/Transport Layer Security). Esse protocolo
criptografa os dados entre cliente e servidor, impedindo que um hacker malicioso
leia os dados que estão trafegando.
Conclusão
Por hora, terminamos nosso estudo de redes. A parte mais importante desse capítulo para o restante do livro é o protocolo HTTP, pois este é o protocolo que o frontend e o backend de uma aplicação costumam usar para se comunicar.
Exercícios
-
Abra um terminal (no Windows, aperte
Win + R, escrevacmde aperteOk) e digite o seguinte comando:(Obs: não precisa escrever
C:\>, é apenas para indicar que deve ser digitado no Prompt de comando)C:\> curl -i sigaa.ufs.brEsse comando irá mostrar uma mensagem HTTP. Responda:
- O tipo da mensagem é uma resposta ou requisição HTTP?
- Qual o status code?
- Quantos e quais os headers?
- Essa mensagem tem corpo? Se sim, qual seu tamanho em bytes?
- O que o corpo diz? Como você mudaria o comando para atender o que o corpo diz?
-
No seu computador, abra um navegador web (Chrome, Firefox, Edge, Brave) e abra uma nova aba. Aperte
F12(ouCtrl + Shift + I), irá aparecer um painel com várias informações. Na parte superior desse painel, tem várias abas. Clique na aba chamadaRede/Network. Essa aba mostra as mensagens HTTP que seu navegador faz enquanto você navega na web.Então, sem fechar esse painel, digite o seguinte na barra de navegação:
example.come aperte Enter.Responda:
- Quantas linhas apareceram?
- Clique na primeira linha. No novo painel aberto, vá na aba
Headers/Cabeçalhos. Quantos headers tem a primeira requisição? Qual o método da primeira requisição? Qual o status code da primeira resposta? - No mesmo painel, determine se a primeira requisição teve uma payload não-vazia. Faça o mesmo para a resposta.
-
Acesse https://httpbin.org/forms/post no seu navegador. Abra novamente o painel de
Rede/Network. Preencha o formulário e envie. Observe a nova mensagem que apareceu após você clicar para enviar. A requisição tem corpo? Se sim, qual seu tamanho? E a resposta?
-
Demultiplexar é o processo de separar um único canal de comunicação em múltiplos canais. É o inverso da multiplexação: combinar diferentes canais em um único canal. ↩
-
Se você não sabe o que é uma API, não se preocupe: descobriremos no capítulo 3. ↩
-
Apesar da maioria dos protocolos da camada de aplicação seguirem o Modelo Cliente-Servidor, existem alguns protocolos que seguem outro modelo, chamado de Modelo Peer-to-Peer (P2P), onde cada host atua como um misto de cliente e servidor, falando em pé de igualdade com outros hosts. O exemplo clássico é o protocolo BitTorrent, mas também existem outros como o protocolo Bitcoin e XMPP/Jabber. ↩
-
Para simplificar, o exemplo mostrado omite os caracteres que representam as quebras de linhas (
\r\n) numa requisição. Esses caracteres são importantes para indicar quando uma seção da requisição termina e outra começa. ↩ -
Os mais atentos podem estar se perguntando qual a necessidade do header
Hostse, para iniciar a comunicação HTTP, o cliente precisa saber antecipadamente qual o host que atuará como servidor. O motivo é que o headerHosté utilizado para implementar uma técnica conhecida como virtual hosting, onde um único host físico, com um único IP, hospeda várias aplicações HTTP diferentes (chamados de hosts virtuais). O host físico que atua como servidor olha o valor desse header para saber qual aplicação/host virtual o cliente está interessado.Inclusive, é por esse motivo que o header
Hosté o único que é obrigatório. Uma requisição sem ele será rejeitada pelo servidor. ↩ ↩2
Ambiente de Desenvolvimento
Imagine a seguinte cena: um grupo de 4 alunos precisa entregar um projeto de software para uma disciplina da faculdade. Cada aluno ficou responsável por uma parte do código. O prazo está apertado. Eles combinaram de trocar as partes pelo WhatsApp.
O primeiro aluno manda o arquivo projeto.zip. O segundo baixa, faz suas
alterações, e manda de volta projeto_v2.zip. O terceiro, que não viu a
mensagem do segundo, fez suas alterações na versão do primeiro e manda
projeto_atualizado.zip. Agora ninguém sabe qual é a versão mais recente. O
quarto aluno tenta juntar tudo à mão, num domingo à noite, e estraga uma função
que já estava funcionando. Resultado: projeto_final_v3_AGORA_VAI.zip.
Se você já passou por algo parecido, sabe que o problema não é competência. É a falta de uma ferramenta adequada para coordenar o trabalho de várias pessoas sobre os mesmos arquivos. Essa ferramenta existe e se chama controle de versão.
Esse capítulo apresenta os conceitos e práticas de controle de versão utilizando o Git, a ferramenta mais utilizada no mundo para esse fim. Depois, veremos como organizar um projeto com vários componentes (backend, frontend web, frontend mobile) dentro de um único repositório.
Controle de Versão
Um sistema de controle de versão (Version Control System ou VCS) é um software que registra as alterações feitas em arquivos ao longo do tempo. Ele resolve três problemas fundamentais:
- Histórico. Você consegue ver exatamente o que mudou, quando mudou e quem mudou. Se algo quebrar, é possível voltar a uma versão anterior.
- Colaboração. Várias pessoas podem trabalhar nos mesmos arquivos ao mesmo tempo sem sobrescrever o trabalho umas das outras.
- Rastreabilidade. Cada conjunto de alterações vem acompanhado de uma mensagem explicando o motivo da mudança. Meses depois, é possível entender por que aquele trecho de código existe.
Existem vários sistemas de controle de versão: CVS, Subversion (SVN), Mercurial e Git. Hoje, o Git é o padrão da indústria. Foi criado em 2005 por Linus Torvalds (o mesmo criador do Linux) para gerenciar o código-fonte do kernel Linux, que já naquela época era um projeto com milhares de contribuidores.
Instalando e Configurando o Git
Antes de tudo, instale o Git:
- Linux (Debian/Ubuntu):
sudo apt install git - macOS:
brew install git - Windows: Instale o Git for Windows. Ele
inclui o Git Bash, um terminal que emula o ambiente Linux. Se você usa o WSL
(recomendado neste livro), instale normalmente via
aptdentro do WSL.
Após a instalação, configure seu nome e e-mail. Essas informações ficam gravadas em cada alteração que você registrar:
git config --global user.name "Maria Silva"
git config --global user.email "maria@example.com"
Você pode verificar a configuração com:
$ git config --global --list
user.name=Maria Silva
user.email=maria@example.com
Repositório
Um repositório (ou repo) é um diretório cujo histórico de alterações é
gerenciado pelo Git. Para transformar um diretório qualquer em um repositório
Git, usamos o comando git init:
$ mkdir meu-projeto
$ cd meu-projeto
$ git init
Initialized empty Git repository in /home/maria/meu-projeto/.git/
A partir desse momento, o Git passa a monitorar esse diretório. Ele cria uma
pasta oculta chamada .git/ que armazena todo o histórico. Você nunca precisa
mexer diretamente nessa pasta.
O Ciclo Básico: Staging e Commit
O Git não registra alterações automaticamente. Você decide o que salvar e quando. O fluxo básico tem dois passos:
- Staging (preparação): você marca quais alterações deseja incluir no próximo registro.
- Commit (registro): você grava essas alterações no histórico, junto com uma mensagem descritiva.
Vamos ver na prática. Suponha que criamos um arquivo index.html dentro do
nosso repositório:
<!DOCTYPE html>
<html>
<head>
<title>Meu Site</title>
</head>
<body>
<h1>Olá, mundo!</h1>
</body>
</html>
Se rodarmos git status, o Git nos diz que existe um arquivo novo que ele
ainda não está rastreando:
$ git status
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
index.html
nothing added to commit but untracked files present (use "git add" to track)
Para incluir esse arquivo no próximo commit, usamos git add:
$ git add index.html
Agora o arquivo está na área de staging (staging area). Podemos confirmar
com git status:
$ git status
On branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: index.html
O Git está dizendo: “quando você fizer o próximo commit, vou incluir a criação
do arquivo index.html”. Para gravar de fato, usamos git commit com uma
mensagem que descreve a alteração:
$ git commit -m "Criar página inicial com título e saudação"
[main (root-commit) a1b2c3d] Criar página inicial com título e saudação
1 file changed, 9 insertions(+)
create mode 100644 index.html
Pronto. A alteração está registrada no histórico. O identificador a1b2c3d é
um hash (resumo criptográfico) que identifica esse commit de forma única. Na
prática, o hash completo tem 40 caracteres hexadecimais (ex:
a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0), mas o Git permite usar apenas
os primeiros caracteres, desde que não haja ambiguidade.
Vários Arquivos, Um Commit
Não é necessário fazer um commit por arquivo. Você pode adicionar vários arquivos à área de staging e registrá-los todos de uma vez. O importante é que cada commit represente uma unidade lógica de mudança: uma alteração coerente, com um propósito claro.
Por exemplo, se você criou um arquivo style.css e modificou o index.html
para referenciá-lo, faz sentido que ambas as alterações entrem no mesmo commit,
pois juntas elas formam uma unidade: “adicionar estilização à página inicial”.
$ git add index.html style.css
$ git commit -m "Adicionar estilização à página inicial"
[main e4f5a6b] Adicionar estilização à página inicial
2 files changed, 18 insertions(+), 1 deletion(-)
create mode 100644 style.css
Visualizando o Histórico
O comando git log mostra o histórico de commits, do mais recente ao mais
antigo:
$ git log
commit e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3 (HEAD -> main)
Author: Maria Silva <maria@example.com>
Date: Mon Aug 11 14:32:10 2026 -0300
Adicionar estilização à página inicial
commit a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
Author: Maria Silva <maria@example.com>
Date: Mon Aug 11 14:15:42 2026 -0300
Criar página inicial com título e saudação
Cada entrada mostra o hash completo, o autor, a data e a mensagem. Para uma
versão mais compacta, use git log --oneline:
$ git log --oneline
e4f5a6b Adicionar estilização à página inicial
a1b2c3d Criar página inicial com título e saudação
Vendo o que Mudou
Antes de fazer um commit, é boa prática verificar exatamente o que mudou.
O comando git diff mostra as alterações que ainda não foram adicionadas à
área de staging:
$ git diff
diff --git a/index.html b/index.html
index 3b18e51..f3c4a12 100644
--- a/index.html
+++ b/index.html
@@ -5,6 +5,7 @@
</head>
<body>
<h1>Olá, mundo!</h1>
+ <p>Bem-vindo ao meu site.</p>
</body>
</html>
As linhas com + foram adicionadas; linhas com - foram removidas. Nesse
caso, adicionamos um parágrafo de boas-vindas.
Branches
Até agora trabalhamos apenas em uma linha reta de commits. Mas imagine que você quer experimentar uma nova funcionalidade sem arriscar quebrar o que já está funcionando. Ou que dois colegas querem trabalhar em funcionalidades diferentes ao mesmo tempo. É para isso que existem os branches (ramos).
Uma branch é uma linha independente de desenvolvimento. Quando você cria uma branch, está criando um espaço isolado onde pode fazer commits sem afetar a branch principal.
A branch padrão geralmente se chama main1. Vamos criar uma
branch nova para adicionar uma página “sobre”:
$ git branch pagina-sobre
Esse comando criou a branch, mas ainda estamos na main. Para mudar para a
nova branch, usamos git checkout:
$ git checkout pagina-sobre
Switched to branch 'pagina-sobre'
Existe um atalho que cria a branch e muda para ela de uma vez:
$ git checkout -b pagina-sobre
Switched to a new branch 'pagina-sobre'
Agora, qualquer commit que fizermos será registrado na branch pagina-sobre,
sem afetar a main. Vamos criar o arquivo sobre.html e fazer um commit:
$ git add sobre.html
$ git commit -m "Adicionar página sobre com informações do projeto"
[pagina-sobre 7d8e9f0] Adicionar página sobre com informações do projeto
1 file changed, 12 insertions(+)
create mode 100644 sobre.html
Se voltarmos para a main, o arquivo sobre.html não existe:
$ git checkout main
Switched to branch 'main'
$ ls
index.html style.css
O arquivo só existe na branch pagina-sobre. As duas branches têm históricos
diferentes a partir do ponto onde divergiram.
Merge
Quando a funcionalidade na branch está pronta e testada, queremos incorporá-la à branch principal. Esse processo se chama merge (mesclagem).
Para mesclar a branch pagina-sobre na main, primeiro vamos para a main
e depois executamos o merge:
$ git checkout main
Switched to branch 'main'
$ git merge pagina-sobre
Updating e4f5a6b..7d8e9f0
Fast-forward
sobre.html | 12 ++++++++++++
1 file changed, 12 insertions(+)
create mode 100644 sobre.html
Agora a main contém tudo o que estava em pagina-sobre. O Git disse
“Fast-forward” porque não houve alterações na main enquanto trabalhávamos
na outra branch. Ele simplesmente avançou o ponteiro.
Após o merge, a branch pagina-sobre pode ser apagada:
$ git branch -d pagina-sobre
Deleted branch pagina-sobre (was 7d8e9f0).
Conflitos
Nem todo merge é tão simples. Quando duas branches alteram a mesma parte do mesmo arquivo, o Git não consegue decidir qual versão manter. Isso é um conflito.
Suponha que dois desenvolvedores, Maria e João, criaram branches separadas a
partir da main. Maria alterou a saudação no index.html para “Olá, pessoal!”
na branch saudacao-maria. João alterou a mesma linha para “Bem-vindos ao
site!” na branch saudacao-joao.
Maria faz o merge primeiro. Tudo corre bem. Agora João tenta fazer o merge da sua branch:
$ git checkout main
$ git merge saudacao-joao
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
O Git nos avisa que houve um conflito. Se abrirmos o index.html, veremos algo
assim:
<!DOCTYPE html>
<html>
<head>
<title>Meu Site</title>
</head>
<body>
<<<<<<< HEAD
<h1>Olá, pessoal!</h1>
=======
<h1>Bem-vindos ao site!</h1>
>>>>>>> saudacao-joao
<p>Bem-vindo ao meu site.</p>
</body>
</html>
O trecho entre <<<<<<< HEAD e ======= é a versão que já está no main
(a alteração da Maria). O trecho entre ======= e >>>>>>> saudacao-joao é a
versão da branch do João. Para resolver o conflito, editamos o arquivo e
escolhemos a versão final. Podemos manter uma, a outra, ou combinar as duas:
<h1>Olá, pessoal! Bem-vindos ao site!</h1>
Depois de editar, removemos os marcadores de conflito (<<<<<<<, =======,
>>>>>>>), adicionamos o arquivo à staging area e fazemos o commit:
$ git add index.html
$ git commit -m "Resolver conflito: combinar saudações de Maria e João"
[main b3c4d5e] Resolver conflito: combinar saudações de Maria e João
Conflitos são normais. Sempre que duas pessoas mexem no mesmo trecho, o Git não tenta adivinhar qual versão é a correta. Ele mostra as duas e deixa a decisão com você.
Colaboração com Repositórios Remotos
Até agora, tudo aconteceu localmente, na sua máquina. Mas para colaborar com outras pessoas, é preciso que o repositório exista em algum lugar acessível por todos. Esse lugar é um repositório remoto (remote).
O serviço mais popular para hospedar repositórios remotos é o
GitHub. Existem outros, como GitLab e Bitbucket, mas
o GitHub é de longe o mais usado. Ao criar um repositório no GitHub, você obtém
uma URL como https://github.com/maria/meu-projeto.git.
Clone
Para obter uma cópia local de um repositório que já existe no GitHub, usamos
git clone:
$ git clone https://github.com/maria/meu-projeto.git
Cloning into 'meu-projeto'...
remote: Enumerating objects: 18, done.
remote: Total 18 (delta 4), reused 18 (delta 4), pack-reused 0
Receiving objects: 100% (18/18), done.
Resolving deltas: 100% (4/4), done.
Isso cria um diretório meu-projeto/ com todo o histórico de commits. O
repositório clonado já vem configurado com um remote chamado origin,
apontando para a URL de onde foi clonado.
Push e Pull
Depois de fazer commits localmente, você envia suas alterações para o remoto com
git push:
$ git push origin main
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 312 bytes | 312.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
To https://github.com/maria/meu-projeto.git
e4f5a6b..b3c4d5e main -> main
O comando git push origin main envia os commits da branch main local para
o remote origin.
Para trazer as alterações que outros membros da equipe enviaram, usamos
git pull:
$ git pull origin main
remote: Enumerating objects: 5, done.
remote: Total 5 (delta 2), reused 5 (delta 2), pack-reused 0
Unpacking objects: 100% (5/5), done.
From https://github.com/maria/meu-projeto.git
b3c4d5e..f6a7b8c main -> origin/main
Updating b3c4d5e..f6a7b8c
Fast-forward
contato.html | 8 ++++++++
1 file changed, 8 insertions(+)
create mode 100644 contato.html
O git pull é equivalente a buscar as alterações do remoto (git fetch) e
mesclá-las na branch local (git merge). Se houver conflitos, o processo de
resolução é o mesmo que vimos antes.
O Fluxo de Trabalho Baseado em Branches
Na prática, equipes raramente fazem commits diretamente na main. O fluxo de
trabalho mais comum segue estes passos:
- O desenvolvedor cria uma branch a partir da
mainpara trabalhar numa funcionalidade específica (ex:adicionar-pagina-contato). - Ele faz seus commits nessa branch.
- Quando a funcionalidade está pronta, ele envia a branch para o remoto
(
git push origin adicionar-pagina-contato). - Ele abre um pull request (PR) no GitHub, que é uma proposta de
incorporar as alterações dessa branch na
main. - Outros membros da equipe revisam o código, fazem comentários e sugerem mudanças.
- Após aprovação, o pull request é mesclado no
main.
Esse fluxo tem duas vantagens importantes. Primeiro, a main nunca recebe
código que não foi revisado por outra pessoa. Segundo, cada funcionalidade é
desenvolvida de forma isolada, reduzindo a chance de conflitos.
.gitignore
Nem todo arquivo do seu projeto deve ser rastreado pelo Git. Arquivos gerados
automaticamente (como node_modules/, que contém dependências baixadas), arquivos
de configuração local do editor (como .vscode/settings.json) e, principalmente,
arquivos com dados sensíveis (como senhas e chaves de API) não devem ir para
o repositório.
Para dizer ao Git quais arquivos ignorar, criamos um arquivo chamado
.gitignore na raiz do repositório:
node_modules/
dist/
.env
*.log
Cada linha define um padrão. Nesse exemplo:
node_modules/ignora o diretório inteiro de dependências.dist/ignora a pasta de arquivos gerados pelo build..envignora o arquivo de variáveis de ambiente (que costuma conter senhas).*.logignora qualquer arquivo com extensão.log.
O .gitignore em si deve ser commitado, pois todos os membros da equipe
precisam ignorar os mesmos arquivos.
Organizando o Projeto: Monorepo
Em Introdução ao Desenvolvimento de Aplicações, vimos que uma aplicação é composta de vários componentes: backend, frontend web, frontend mobile, banco de dados. Cada um desses componentes tem seu próprio código, suas próprias dependências e seus próprios scripts de execução.
Uma pergunta natural é: cada componente deve ter seu próprio repositório, ou todos devem ficar juntos em um único repositório?
Monorepo vs. Multi-repo
Existem duas abordagens principais:
- Multi-repo: cada componente vive em um repositório separado. O backend
fica em
github.com/equipe/backend, o frontend web emgithub.com/equipe/web, o mobile emgithub.com/equipe/mobile. - Monorepo: todos os componentes vivem no mesmo repositório, em diretórios separados.
A abordagem multi-repo parece mais organizada à primeira vista, mas na prática ela cria problemas de coordenação. Quando uma mudança no backend exige uma mudança correspondente no frontend (por exemplo, renomear um campo da API), você precisa abrir dois pull requests em repositórios diferentes, e garantir que os dois sejam mesclados juntos. Versões ficam dessincronizadas. Configurações são duplicadas.
A abordagem monorepo simplifica isso: uma única alteração que corta o backend e o frontend fica em um único commit, um único pull request, uma única revisão. A equipe inteira vê o projeto como um todo, não como peças separadas.
A estrutura típica de um monorepo para uma aplicação com backend, web e mobile é:
meu-projeto/
├── backend/
│ ├── src/
│ ├── package.json
│ └── tsconfig.json
├── web/
│ ├── src/
│ ├── package.json
│ └── tsconfig.json
├── mobile/
│ ├── src/
│ ├── package.json
│ └── app.json
├── .gitignore
└── README.md
Cada diretório (backend/, web/, mobile/) é um componente independente,
com suas próprias dependências e configuração. Mas todos vivem sob o mesmo
repositório Git, compartilhando o mesmo histórico e o mesmo processo de revisão.
É essa a estrutura que o TamoJunto seguirá. Quando chegarmos ao próximo capítulo, vamos montar o repositório com esses três pontos de entrada.
Quando usar multi-repo
Monorepo não é sempre a melhor escolha. À medida que o projeto cresce e equipes diferentes ficam responsáveis por componentes diferentes, um monorepo pode se tornar lento (muitos arquivos, histórico grande) e ruidoso (notificações sobre partes do código que não te interessam). Grandes empresas como Google e Meta usam monorepos com ferramentas especializadas para lidar com essa escala. Projetos menores, como o que construiremos neste curso, se beneficiam da simplicidade do monorepo sem sofrer essas desvantagens.
A regra é simples: se a equipe é pequena e os componentes mudam juntos, comece com monorepo. Se os componentes forem verdadeiramente independentes (equipes diferentes, ciclos de deploy diferentes, nenhuma interface compartilhada), multi-repo faz mais sentido.
Conclusão
Com o Git, o cenário do projeto_final_v3_AGORA_VAI.zip deixa de existir.
Cada alteração é rastreada, cada decisão fica registrada, e duas pessoas podem
trabalhar no mesmo arquivo sem medo. O fluxo de branches e pull requests garante
que código é revisado antes de entrar na branch principal.
A partir do próximo capítulo, vamos colocar isso em prática: criaremos o repositório do TamoJunto como um monorepo com backend, web e mobile.
Exercícios
-
Instale o Git na sua máquina e configure seu nome e e-mail usando
git config. Verifique comgit config --global --listque as informações estão corretas. -
Crie um diretório chamado
exercicio-git, inicialize um repositório Git nele (git init) e crie um arquivonotas.txtcom qualquer conteúdo. Faça um commit com a mensagem “Adicionar arquivo de notas”. Verifique o histórico comgit log --oneline. -
No mesmo repositório, crie uma branch chamada
experimento. Nela, modifique o arquivonotas.txte faça um commit. Depois, volte àmaine faça uma modificação diferente na mesma linha denotas.txt. Tente fazer o merge. Resolva o conflito que surgir. -
Crie um repositório no GitHub (ou GitLab). Conecte seu repositório local a ele com
git remote add origin <URL>. Envie seus commits comgit push. Verifique no site que os arquivos aparecem. -
O arquivo
.gitignoreabaixo tem um problema. Identifique qual é e explique o risco:node_modules/ dist/ *.log
Referências
Para um estudo mais aprofundado sobre Git, recomendo o livro Pro Git, de Scott Chacon e Ben Straub, disponível gratuitamente em português em https://git-scm.com/book/pt-br/v2.
-
Historicamente, a branch padrão se chamava
master. Em 2020, o GitHub e outras plataformas mudaram o padrão paramain. Ambos os nomes funcionam da mesma forma. A escolha é apenas de convenção. ↩