# Introdução

Um guia prático para quem quer entender os fundamentos da Web

Neste guia completo sobre Web, vamos explorar os conceitos e fundamentos que formam esta tecnologia, ou melhor dizendo, este conjunto de tecnologias, a *World Wide Web*.

### Outro guia sobre Web?

Na verdade, este guia não será superficial. O intuito aqui é explorar as partes internas e principais ferramentas que compõem ou sustentam a web.&#x20;

Iremos entrar nos detalhes, mas tentando trazer **uma didática simples** de fácil compreensão. Não espere que este guia seja uma sopa de letrinhas confusa. Pelo contrário, a ideia é explicar cada passo para que qualquer pessoa na área de tecnologia, seja iniciante ou com algum tempo de experiência, possa compreender.&#x20;

![](/files/bgnnutrPJyYeI8W7cxPs)

### Conteúdo proposto

Primeiro vamos passar por uma breve história dos computadores modernos. Este passo é importante para entendermos o problema que, décadas mais tarde, os *Sistemas Operacionais (ou SO's)* vieram resolver.&#x20;

Pontuo que o objetivo aqui não é explorar **tudo** o que um sistema operacional faz, mas apenas um dos principais problemas que resolve. Com isto, podemos avançar e entender a natureza dos processos em SO's e o que estes utilizam para comunicação uns com os outros (inter-process communication, ou **IPC**).

Dentro da etapa de comunicação entre processos, vamos ver as formas primitivas de comunicação, que são a base para compreendermos como dois processos (programas) distintos se comunicam numa rede global de computadores.&#x20;

Aí é que entra a *Internet* e seus protocolos de comunicação e envio de mensagens.&#x20;

Após compreendermos este importante passo, é hora de explorarmos a riqueza de mensagens enviadas pela internet (conteúdo hypertext, multimedia), que é onde surgem termos como HTTP e característica stateless do protocolo, com seus desafios e formas de resolver problema de navegação Web.

Finalmente, poderemos finalizar este guia com a introdução de mensagens especiais no HTTP, compostas por HTML, CSS e Javascript. Ou seja, a **World Wide Web**.

### Mas antes, que tal um cafezinho?

Além deste guia, eu costumo escrever outros guias e artigos que estão em [meu site](https://leandronsp.com/), então se gosta do meu conteúdo técnico, consideraria me pagar um cafezinho? Gratidão <3

<figure><img src="/files/r6CBsswML1G441WNQDiM" alt="" width="283"><figcaption></figcaption></figure>

Ou copia e cola:

```
00020126850014BR.GOV.BCB.PIX013638ee4bde-574b-4197-b10f-68742087b00b0223Gratidão pelo cafezinho5204000053039865802BR5925Leandro Freitas Maringolo6009SAO PAULO62140510qrN6Ov1wRl63041A3C
```

Agora sim, vamos a isto!


# Breve história dos computadores modernos

### Anos 40

Nesta década, os primeiros computadores considerados "modernos" eram capazes de executar programas escritos em [cartões perfurados](https://en.wikipedia.org/wiki/Punched_card), ou *punched cards*.&#x20;

Os computadores executavam **um programa por vez** e, por causa das velocidades modestas (cerca de 100 operações por segundo), os programas demoravam dias ou *semanas* para serem executados até ao fim.&#x20;

As pessoas que escreviam programas em cartões perfurados tinham, então, de esperar em longas filas para que seus respectivos programas pudessem ser inseridos nos computadores.&#x20;

![](/files/tEpaLTnTCeEuzM1o1lP6)

### Anos 50

Uma década depois, os computadores foram evoluídos para que pudessem **enfileirar** vários programas de uma vez, assim as pessoas não teriam que esperar para inserir os programas: bastava colocar todos eles e o computador fazia o resto do trabalho.&#x20;

Apesar de termos resolvido a questão das *filas de pessoas* que desenvolvem programas, ainda assim os programas eram executados em ordem, **um de cada vez.**&#x20;

Para entendermos o problema existente nesta época, vamos lembrar que um computador moderno é composto por:

* CPU (unidade central de processamento)
* Memória de trabalho (espaço temporário utilizado pela CPU para executar as tarefas)
* I/O: onde **input** pode ser por ex. o teclado, e **output** pode ser a impressora ou o screen (tela)

![](/files/Ysw30Mc0bHTVbxEgNMtT)

Tomemos por cenário 3 programas enfileirados: programa A, programa B e programa C. Enquanto o programa A utiliza o computador, os outros 2 **ficam na fila à espera**.&#x20;

<figure><img src="/files/Pd3RfFfpwraHmM0keOYP" alt=""><figcaption></figcaption></figure>

Tudo bem até aqui.

Mas vamos imaginar que, após utilizar a CPU para fazer seus cálculos, o **programa A** precisa imprimir o resultado na impressora. Esta saída (I/O) leva seu tempo, e enquanto o programa A *espera* no I/O, a CPU não está sendo utilizada.&#x20;

<figure><img src="/files/QS3cN848beg0nFSmt4o2" alt=""><figcaption></figcaption></figure>

Esta *não-utilização* da CPU é por si só um **desperdício de recursos**. Por quê não deixar outro programa na fila (por ex. o programa B) utilizar a CPU enquanto o programa A fica à espera de I/O? Seria um raciocínio sensato, correto?

### Anos 60

Também conhecida como **a era dos** [**transistores**](https://pt.wikipedia.org/wiki/Trans%C3%ADstor) ou *third-generation computers*, nesta década começamos a ver computadores cada vez menores e mais rápidos.&#x20;

O objetivo aqui é justamente resolver o problema da *ociosidade da CPU* das décadas anteriores. Enquanto um programa espera por I/O, outro programa pode utilizar a CPU, fazendo então com que o número de programas executados dentro de um intervalo de tempo aumente. A esta taxa chamamos de **troughput**, ou apenas *tput*.&#x20;

![](/files/rUxqwNYM9nuigAHdt0Nn)

#### Concorrência

Isto significa que dois programas podem ***concorrer** a recursos físicos* do mesmo computador. Mas como isto é feito na prática?

#### Monitors

Os Monitors eram formas primitivas do que hoje conhecemos por [Sistemas Operacionais](https://en.wikipedia.org/wiki/Operating_system). Eles eram capazes de controlar os programas enfileirados e garantir que cada um deles utilizasse uma quantia *justa* da CPU enquanto outros ficavam à espera de I/O.&#x20;

Tal técnica se chama [time-sharing multitasking](https://en.wikipedia.org/wiki/Time-sharing).&#x20;

![](/files/6bIYSLITqWfzMs6hGPpQ)

### 70s

Esta é a década onde os *Monitors* foram evoluídos para **Sistemas Operacionais** (SO). Alguns rotulam como a *era da explosão cambriana* dos computadores, pois, a partir desta década, cada vez mais e mais SO's são criados.&#x20;

Dentre os SO's criados nesta época, vamos dar destaque ao [UNIX](https://pt.wikipedia.org/wiki/Unix), que será utilizado como base neste guia.

![](/files/ZFyakW9BmPzD1RzcA0sf)

**UNIX** foi um sistema muito importante para entendermos onde estamos hoje. Muitas distribuições de SO baseadas em Linux e BSD utilizam a [**filosofia UNIX**](https://en.wikipedia.org/wiki/Unix_philosophy), e portanto são chamados de sistemas *UNIX-like*.&#x20;

#### Concorrência de recursos

SO's modernos baseados em UNIX utilizam a mesma técnica primitiva de *time-sharing multitasking* observada nos *Monitors* dos anos 50/60.&#x20;

Tendo por base a **concorrência** de recursos, e pelo fato da memória de trabalho do computador ser *global*, é preciso garantir que **dois processos concorrentes não utilizem o mesmo espaço de memória**, caso contrário, tais programas entrariam numa condição de conflito de race condition chamada [data race](https://en.wikipedia.org/wiki/Race_condition#Data_race).

![](/files/RbMufWKG3VQpWoiuuZmu)

#### Isolamento dos programas

Para evitar data race, os SO's então têm de garantir algumas propriedades enquanto programas concorrem aos recursos físicos:

* o programa precisa ser **isolado**, ou seja, ter seu espaço de memória reservado onde nenhum outro programa possa ver ou alterar
* o programa pode eventualmente ter a necessidade de se **comunicar com outros programas** através do envio de mensagens
* para atingir tal comunicação, os programas precisam ter **identificadores únicos** para serem reconhecidos no sistema operacional

![](/files/RNhrtHWImOe3qZxFm1kP)

Temos então o resumo das características para um programa existir dentro de um sistema operacional moderno:

1. Isolamento
2. Comunicação por mensagens
3. Identificador único

Estas propriedades formam o conceito de **Processo**.&#x20;

#### Processos

Os processos do SO são uma representação dos programas, com identificador único (PID) e canais de comunicação padrão para envio de mensagens, também chamado de *inter-process communication*, ou **IPC**.&#x20;

Vamos listar todos os processos no sistema operacional. No meu exemplo, estou dentro de um container Docker com **Ubuntu** (sim, depois teremos outro guia especial apenas pra falar sobre containers e Docker).&#x20;

Utilizamos então o comando [ps](https://www.man7.org/linux/man-pages/man1/ps.1.html) que faz parte do SO e lista todos os processos:

```bash
$ ps -e

  PID TTY          TIME CMD
    1 pts/0    00:00:00 bash
   15 ?        00:00:00 ps
```

Agora, abrimos outro terminal e executamos o comando `sleep 30`, que bloqueia o processo e fica **à espera do relógio durante 30 segundos:**

*Terminal 1*

```bash
$ sleep 30
```

Em outro terminal, verificamos todos os processos:

*Terminal 2*

```bash
$ ps -eo pid,pcpu,comm,tty

PID %CPU COMMAND TTY
  1  0.0 bash    pts/0
 16  0.1 bash    pts/1
103  0.7 sleep   pts/0
109  0.0 ps      ?
```

Okay, vamos aos detalhes:

* `-e` é a opção para listar os processos utilizando a sintaxe standard do SO
* `-o pid,pcpu,comm` é a opção para mostrar as colunas PID, CPU e CMD
* o processo de `PID 1` é o programa **bash** aberto no primeiro terminal (TTY pts/0)
* o processo 16 é o programa bash aberto no segundo terminal (TTY pts/1)
* o processo 103 é o programa **sleep** que está *bloqueado* a espera do relógio terminar. Este programa está rodando no primeiro terminal (TTY pts/0)
* o processo 109 se refere ao próprio programa `ps` que mostrou a saída e **terminou**. Lembrando que todos os comandos são programas no SO.&#x20;

Repara que o processo sleep, enquanto está à espera de terminar o relógio, não utiliza **quase nada** da CPU. Esta operação é puramente I/O!

Agora, vamos simular um processo que faça uso de CPU. No primeiro terminal, vamos utilizar um loop infinito (que em termos computacionais, utiliza um ciclo de CPU para cada interação do loop):

```bash
$ while true; do true; done
```

Em um segundo terminal:

```bash
$ sleep 30
```

E, finalmente, no terceiro terminal:

```bash
$ ps -eo pid,pcpu,comm,tty

 PID %CPU COMMAND         TT
   1 57.0 bash            pts/0
  15  0.0 bash            pts/1
  64  1.2 bash            pts/2
  77  0.5 sleep           pts/2
  80  0.0 ps              ?
```

Podemos notar que o processo 1 do primeiro terminal (pts/0) utiliza 57% da CPU, enquanto o processo 77 (sleep) do segundo terminal (pts/2) está bloqueado no I/O sem utilizar a CPU.&#x20;

**Processos concorrentes!!!!!!1**

Mas como o SO faz a gestão destes processos?

#### Escalonamento preemptivo de processos

O sistema operacional traz um programa especial chamado *escalonador*, mais conhecido como **OS Scheduler**.&#x20;

Este programa é responsável por fazer todo o trabalho de **time-sharing multitasking**, garantindo que os processos concorram com a CPU de forma justa.&#x20;

Quando um processo fica a espera de I/O, outro processo pode utilizar a CPU. Entretanto, o SO nem sempre deixa um processo utilizar a CPU até terminar seu trabalho. Na maioria das vezes, o escalonador interrompe o processo da CPU depois de um **tempo**, estipulado no próprio escalonador.&#x20;

Assim que um processo é *interrompido*, ele é colocado numa fila de espera juntamente com outros processos, abrindo oportunidade de **preempção** para que o próximo processo na fila possa fazer uso da CPU.&#x20;

Este trabalho feito pelo escalonador é chamado de **troca de contexto**, ou *context switch*.&#x20;

<figure><img src="/files/QNG6KGVtrqr8HvdBKBlt" alt=""><figcaption></figcaption></figure>

Os critérios para interrupção podem variar, mas quando um SO traz escalonador que interrompe processos baseado no tempo de execução com preempção, é comum chamarmos de [escalonador preemptivo](https://en.wikipedia.org/wiki/Preemption_\(computing\)), ou **preemptive scheduler**.

#### Escalonamento cooperativo de processos

Existe outra forma de fazer escalonamento, que é baseada na **transferência de controle da troca de contexto** do escalonador para o próprio processo, ou seja, o processo recebe a responsabilidade de saber quando a troca de contexto deve acontecer. A este tipo de escalonamento, chamamos de **escalonamento cooperativo**, ou *cooperative scheduler.*

A maioria dos SO's modernos fazem uso do escalonamento preemptivo. Mas algumas implementações chegaram a ter o modo cooperativo, como no caso do antigo MS-DOS.&#x20;

Alguns runtimes de linguagens de programação também acabam por implementar um escalonamento cooperativo, sendo esta uma decisão de design de determinada linguagem de programação.&#x20;

Okay, vimos até agora que os sistemas operacionais foram criados para que múltiplos programas pudessem concorrer aos recursos físicos do computador de forma justa, *maximizando utilização dos recursos, evitando desperdícios e aumentando a taxa de conclusão dos programas* (throughput).

Aprendemos também que processos de SO's são abstrações que encapsulam programas com um identificador e são escalonados no SO para concorrerem à CPU **mediante troca de contexto**.&#x20;

### Anos 70-90

Estas 3 décadas foram um marco na tecnologia, pois os sistemas operacionais permitiram que diversas linguagens de programação pudessem ser criadas, causando impacto direto na forma como diferentes empresas tomavam decisões estratégicas.

Com isto, novas formas de criar programas foram desenvolvidas, dentre elas a capacidade de escrevermos programas onde **determinados trechos de código** pudessem concorrer separadamente na CPU.&#x20;

Ou seja, enquanto o processo principal concorre à CPU, um trecho **dentro do mesmo processo** pode também concorrer de forma independente, assim um único processo poderia permitir que múltiplos blocos de código utilizem a CPU de forma concorrente e justa.&#x20;

Seria uma forma de dizer ao sistema operacional:

> Hey, SO, sou o processo 42, mas tenho aqui um trecho de código numa estrutura que tem acesso à minha memória, mas que esta estrutura quer também ficar na fila de escalonamento para utilização da CPU.
>
> Você poderia criar esta estrutura aí pra mim, \~senhor sistema operacional?

A esta estrutura chamamos de **threads**.&#x20;

#### Threads

As threads podem ser vistas como unidades de concorrência assim como os processos, mas como estas vivem **dentro de um processo**, logo estão utilizando a mesma memória do processo, mas sem carregar *todo* o processo junto com elas.&#x20;

Ou seja, threads são mais leves que processos. Assim, é possível escrever um programa com múltiplas threads concorrentes, também chamado de **multi-threading**.&#x20;

Então quer dizer que uma thread, por ser mais leve, é **melhor** que um processo?

Não necessariamente, pois duas threads diferentes do mesmo processo compartilham da mesma memória do processo principal. Ou seja, estão sujeitas a **race condition**! (mas vamos falar de race conditions e formas de mitiga-las mais pra frente).

Para exemplificar que threads são criadas no sistema operacional, vamos expor a coluna `TID` no output do comando `ps`:

```bash
$ ps -eo pid,tid,pcpu,comm

 PID   TID %CPU COMMAND
   1     1 12.0 bash
  15    15  0.0 bash
  64    64  0.0 bash
 101   101  1.2 sleep
 104   104  0.0 ps
```

* a coluna TID se refere a "thread ID"
* no exemplo acima, TID = PID, pois cada processo tem uma thread principal. Os processos descritos acima não estão criando novas threads.&#x20;

Isto mostra que o sistema operacional traz estas duas unidades primitivas de concorrência para nossa utilização: **processos e threads**.

### Lei de Moore

Gordon Moore constatou com [estudos](https://en.wikipedia.org/wiki/Moore%27s_law), por volta de 1970, que em termos físicos, as CPU's iriam dobrar de performance a cada 18 meses. Ou seja, o número de ciclos que uma CPU faz por segundo iria dobrar a cada 1 ano e meio.&#x20;

E isto foi uma verdade durante décadas. A indústria viu a explosão de sistemas criados por diversas linguagens de programação onde, mesmo que o sistema fosse escrito de forma não-performática, aquilo não seria um problema para os programadores pois podia-se colocar "mais CPU" para suportar escala.&#x20;

### Anos 2000

A partir dos anos 2000, os engenheiros de CPU's se depararam com um problema físico. Com clocks de CPU's chegando a mais de 4GHz, se os ciclos continuassem aumentando, isto ocorreria uma geração de energia enorme, podendo haver efeitos colaterais graves.&#x20;

Isto colocou para trás a Lei de Morre, mas desafiou a indústria de CPU's a buscar uma solução. Os engenheiros então chegaram a uma solução onde, ao invés de aumentar a velocidade da CPU, construir-se-iam mais núcleos utilizando a mesma velocidade.&#x20;

Começara então a **era multi-core**.

#### Era multi-core e paralelismo

Meados dos anos 2000 marcou muito o conceito de multi-core. Era comum vermos CPU dual-core, mais tarde quad-core, 8-core e assim por diante. Hoje é comum vermos CPU's com 8, 16, 32 ou até mesmo 128-cores!

Com multi-core, vem então a mudança de paradigma de desenvolvimento de sistemas. Para ter um máximo proveito da CPU, precisamos escrever programas que saibam utilizar **todos os núcleos (cores) da CPU** de forma eficiente.&#x20;

Os processos do SO, devido ao seu isolamento por definição, são escalonados de forma **paralela** na CPU, onde 2 processos distintos concorrem **ao mesmo tempo** em dois núcleos dentro da mesma CPU.&#x20;

Mas com as threads, que compartilham memória do mesmo processo e **estão sujeitas a race condition**, o *desafio* para quem escreve programa multi-threading é maior.

### Desafios em sistemas concorrentes (multi-threading)

Vamos agora focar nos desafios e algumas soluções que a indústria tem por padrão no que tange a multi-threading.&#x20;

#### Race condition

Como vimos anteriormente, os processos são por natureza isolados e portanto não estão sujeitos a race conditions, diferente das threads.&#x20;

Mas em um cenário onde há risco de race condition em um sistema multi-threading, como evitar tal efeito colateral?

#### Thread Lock

O sistema operacional fornece um mecanismo onde, caso uma thread vá utilizar CPU e precise garantir que não há data race, esta pode **pedir um lock** ao SO. Assim, outras threads do mesmo processo *ficarão à espera* até que o lock seja liberado.&#x20;

Quando uma thread é suspensa na troca de contexto, *caso tenha um lock, este é liberado.*&#x20;

Definir se uma thread vai ou não utilizar um lock não é responsabilidade do SO, isto é totalmente delegado para quem escrever o sistema, no caso a pessoa que escreve o código multi-threading.&#x20;

Utilizar locks mitiga race conditions, mas traz alguns efeitos colaterais que precisam ser levados em conta:

* se uma thread tem o lock e entretanto por algum motivo ela é abruptamente terminada, outras threads que dependem do mesmo lock ficam bloqueadas, caindo em uma situação de **deadlock**
* a utilização de muitos locks afeta a performance do sistema. Muitos locks acabam por tornar o multi-threading redundante, pois como criar lock no SO tem seu *custo*, há situações em que seria melhor nem usar multi-threading de todo.

![deadlock](/files/FCG9i6fz2032899Zr8z9)

Apesar dos efeitos colaterais, a utilização de locks com prudência tem seu valor em diversas ocasiões, dependendo do tipo de problema.

#### Lock otimista

Uma alternativa aos locks explícitos é a utilização de um lock **otimista** (optimistic locking), que basicamente é a criação de duas ou mais versões dos dados, que são então *comparados* antes do update por 2 threads concorrentes. Desta forma, como não há lock explícito, também não há o risco de deadlocks.&#x20;

#### Modelo de atores

Outra alternativa aos locks é fazer com que a thread seja **safe**. Ou seja, que tenha seu próprio espaço na memória tal como um processo. Mas teoricamente é impossível uma vez que a thread compartilha o mesmo espaço físico do processo.&#x20;

Então, técnicas de cópia dos dados para outro espaço disponível na memória aparecem. Mesmo dentro do mesmo processo, duas threads diferentes têm cada uma seu **estado privado**, e desta forma uma não consegue modificar o estado da outra.&#x20;

Com o estado interno isolado, em um sistema complexo, uma thread precisa se comunicar com outras, tal como os processos comunicam entre si utilizando IPC (veremos este tema de IPC mais tarde). Esta comunicação entre threads também deve ser feita por envio de mensagens.&#x20;

Estas 2 características são as principais que fazem com que tal thread seja denominada por **ator**. Com isto, modelo de atores é uma alternativa a lock explícito e lock otimista em sistemas multi-threading.&#x20;

### E o I/O?

Nesta seção focamos bastante na concorrência pela CPU e na sua evolução com técnicas e funcionalidades como Threads, locks e modelo de atores.&#x20;

Mas pouco falamos daqueles processos que, lá atrás nos anos 60, ficavam bloqueados à espera de I/O, correto?

Ficar bloqueado a espera de I/O é algo inevitável, pois foge ao controle do programa e do sistema computacional. É exatamente esperar pelo teclado, pelo screen, pelo relógio, pela impressora ficar pronta, pelos dados chegarem na rede (sim, networking utiliza I/O), entre outros aspectos.

Mas, diferente da guinada que as CPU's tiveram para uma arquitetura **multi-core**, os mecanismos de armazenamento e rede tais como HD, SSH, largura de banda, fibra ótica, etc têm ficado **cada vez mais rápidos e baratos.**&#x20;

#### **Non-blocking I/O**

Sistemas operacionais modernos fornecem funcionalidades onde podemos verificar se o recurso I/O está disponível para leitura ou escrita.&#x20;

Esta verificação pode acontecer de forma **assíncrona**, e assim a thread ou processo que vai utilizar I/O não precisa ficar à espera. O processo fica então disponível para outras operações, e assim que o recurso ficar disponível, é *notificado* e pode portanto ter o resultado do I/O.

Para que isto seja possível, é necessária a implementação de um [padrão de loop de eventos](https://en.wikipedia.org/wiki/Event_loop), e dentro desse loop são feitas as tais verificações de recursos I/O disponíveis com suas respectivas "threads interessadas".&#x20;

Tal implementação não é feita por todos os runtimes, mas nas últimas décadas temos visto uma crescente nisto, principalmente depois do sucesso que o runtime NodeJS teve nesta área. Atualmente, outros runtimes e projetos começaram a ir pelo mesmo caminho de non-blocking I/O (ou I/O assíncrono, que são termos ligeiramente diferentes mas se convergem), tais como Java Loom, PHP Swoole e Ruby 3.&#x20;

#### Escalonamento cooperativo

Vimos anteriormente que os sistemas operacionais têm, em sua maioria, um tipo de escalonamento *preemptivo*, e que a forma cooperativa (quando a própria thread ou processo controla a troca de contexto) pode ser utilizada em alguns casos .

Em arquitetura de I/O não bloqueante, este modelo **cooperativo** pode ter suas vantagens, uma vez que a responsabilidade de **trocar o contexto fica por conta do processo** que está a espera de I/O e é notificado quando o recurso está pronto.&#x20;

Mas não é todo runtime que decide implementar modelo cooperativo de escalonamento. Por exemplo, o Ruby 3 traz uma interface de escalonador cooperativo, mas não implementa dentro do runtime. Fica a cargo de quem desenvolve implementar um seguindo a interface de chamadas aos recursos de I/O do sistema operacional.

### Resumo

Esta jornada da breve história dos computadores foi um pouco extensa, mas bastante resumida face à quantidade de conteúdo que temos nesta área.&#x20;

Vamos continuar com o guia e seu objetivo que é continuar dissecando as partes fundamentais da **Web**.&#x20;

Na próxima seção abordaremos como *diferentes processos se comunicam* e quais as formas comuns de comunicação entre processos dentro de um sistema operacional.


# Comunicação entre processos

Uma vez que entendemos como os computadores evoluíram para um modelo onde **múltiplos programas podem ser executados de forma concorrente,** e que a principal unidade de concorrência em um sistema operacional é o **processo**, como podemos fazer dois processos independentes comunicarem entre si? &#x20;

Afinal, um processo isolado que não conversa com outros processos não serve para muita coisa...

<figure><img src="/files/MXvE6eXBX8zErmHnLm5g" alt=""><figcaption></figcaption></figure>

Pelo motivo de serem isolados e terem seu próprio espaço em memória, para comunicação processos precisam compartilhar um *canal de comunicação*, ou **communication channel.**

É onde entramos então num conceito muito importante chamado *Inter-process communication*, ou **IPC**.

## IPC

Existem diversas formas de IPC, mas vamos destacar *algumas* delas, que serão abordadas neste guia.

* File descriptors
* UNIX Pipes
* UNIX Sockets

Antes de entrarmos em detalhe sobre cada uma das **formas de comunicação** acima, vamos entender como um computador armazena dados *persistentes de longa duração*.&#x20;

### Como os dados persistentes são armazenados

Sabemos que o computador possui uma *memória de trabalho* onde diferentes programas podem utilizá-la durante suas execuções. Mas esta memória é volátil e não serve para armazenar dados de longa duração.

Então, utilizamos outro dispositivo físico para isto, que geralmente é chamado de **HDD** (Hard disk drive), ou simplesmente **HD**. Ou então, os mais modernos **SSD**. Independente do dispositivo I/O, o sistema operacional utiliza um sistema de persistência e busca baseado em *intervalos*, ou ranges.&#x20;

Como conseguimos aceder a uma informação no dispositivo? Em termos físicos, o sistema operacional precisa saber o intervalo entre o começo da informação e seu término. A este intervalo, damos o nome de **file,** ou *arquivo*.

Se agruparmos todos os *arquivos/files* do sistema operacional em um sistema composto por dicionários, temos o que é chamado de **filesystem**.

<figure><img src="/files/PJSp3BhBoQnNlgVREKo5" alt=""><figcaption></figcaption></figure>

Sabendo que cada processo é isolado na memória de trabalho, não nos resta outra chance para fazer ***IPC*** a não ser utilizando o *filesystem*.&#x20;

Vamos então, explorar diferentes formas de IPC, começando por **file descriptors**.


# File descriptors

File descriptor (ou **fd**) é um arquivo especial que é organizado em uma *file descriptor table* e possui um **identificador único,** que aponta uma referência seja para um dispositivo físico I/O, um arquivo ou até mesmo outro *file descriptor*.

### Standard streams

Todo processo UNIX tem por default 3 *communication channels*, também conhecidos por **standard streams**, que são representados por file descriptors (fd):

* `fd 0`: standard input, ou **STDIN**
* `fd 1`: standard output, ou **STDOUT**
* `fd 2`: standard error, ou **STDERR**

<figure><img src="/files/z7kCxJIJCowZt1MCWgUZ" alt=""><figcaption></figcaption></figure>

#### STDOUT

Vamos analisar o seguinte comando:

```bash
$ echo 'Hello'
Hello
```

* O programa `echo` envia uma mensagem para o **STDOUT**, ou seja, *file descriptor 1*
* STDOUT refere-se ao screen do computador, e por isto a mensagem `Hello` é mostrada na tela

#### STDIN

Agora, vamos a outro exemplo com *STDIN*:

```bash
$ base64
```

Este comando fica à espera de dados em uma *interface interativa* do **STDIN,** que refere-se ao teclado do computador.&#x20;

Então, devemos digitar qualquer texto (neste caso digitei "leandro") e, para finalizar, devemos pressionar a tecla *ENTER\[CR]* seguida de **CTRL+d**, que basicamente finaliza a interface STDIN. Após isto, o comando deve retornar o output:

```bash
bGVhbmRybwo=
```

* O programa `base64` fica à espera do **STDIN**, ou seja, *file descriptor 0*
* STDIN refere-se ao teclado do computador, e por isto abre-se uma *interface interativa* para receber informação a partir do teclado
* Digitei "leandro", pressionei **ENTER** e em seguida **CTRL+d** para sair da interface STDIN
* O programa capturou a informação através do *STDIN* e converteu a informação para um formato *base 64*
* O programa enviou a informação convertida em base 64 para o *STDOUT*
* STDOUT refere-se ao screen do computador, e por isto o conteúdo base 64 é mostrado na tela

Interessante notar que programas tais como o `base64` interagem tanto com o stream STDIN quanto o STDOUT.

#### STDERR

E se passarmos um argumento, por exemplo o nome de um arquivo inexistente para o comando `base64`?

```bash
$ base64 name.txt
Unable to open 'name.txt': No such file or directory
```

Como o programa fica à espera de informação a partir do STDIN, é lançado um erro caso o STDIN não proveja a informação.&#x20;

Todo programa, por padrão, envia seus erros para a stream **STDERR**, representada pelo *file descriptor 2*.&#x20;

E qual o dispositivo de saída do ***STDERR***? É o mesmo do *STDOUT*, e justamente por este motivo conseguimos ver a mensagem de erro na tela!

### Redirecionamento de streams

Recapitulando, sabemos que *file descriptors (fd)* são arquivos especiais do *filesystem*, mas para todo efeito, são **arquivos**.&#x20;

E, uma vez que standard streams são file descriptors, será possível *redirecionarmos*, ou seja, **mudarmos o destino** de um stream para outro stream ou até mesmo qualquer outro arquivo do *filesystem*?&#x20;

Sim, é possível, com o uso dos operadores `>` e `<`, utilizando o número do *fd* como prefixo.

* Redirecionamento de **STDIN**: `<`, ou `0<`
* Redirecionamento de **STDOUT**: `>`, ou `1>`
* Redirecionamento de **STDERR**: `2>`

Voltando ao exemplo do comando `echo`, podemos redirecionar o STDOUT para outro arquivo, e desta forma, o output será enviado para o arquivo de redirecionamento:

```bash
$ echo leandro 1> name.txt
```

Note que o output não foi mostrado, pois foi redirecionado para o arquivo `name.txt`. Se executarmos o comando `cat name.txt`, podemos ver o resultado no screen!

Voltando ao exemplo do base64, que tal redirecionarmos o STDIN default (dispositivo de teclas) para que seja a partir do arquivo contendo o nome?

```bash
$ base64 0< name.txt
bGVhbmRybwo=
```

Nice!&#x20;

Um "açúcar sintático" no bash é que, quando quisermos redirecionar o *STDIN* ou *STDOUT*, **não precisamos colocar o sufixo** do *file descriptor*:

```bash
$ echo leandro > name.txt # default do > é 1
$ base64 < name.txt       # default do < é 0
bGVhbmRybwo=
```

E se tentarmos com um arquivo não-existente?

```bash
$ base64 blah.txt
Unable to open 'blah.txt': No such file or directory
```

Muito similar ao erro que tivemos anteriormente. Como o arquivo **não é encontrado**, é lançado um erro, que vai para o *STDERR*, que por sua vez é mostrado por *default* no screen.

Mas podemos redirecionar o **STDERR** também para um outro arquivo, tal como o *STDOUT e STDIN*?&#x20;

Sim, mas no caso do STDERR, precisamos utilizar o sufixo do file

```bash
$ base64 blah.txt 2> err.txt
```

Confirmando que o conteúdo foi redirecionado para `err.txt`:

```bash
# Como o cat é um comando que também fica à espera de STDIN, 
#  também funciona se utilizarmos redirecionamento de STDIN
#  Exemplos:
#    cat 0< err.txt
#    cat < err.txt
# Ou, simplesmente:
$ cat err.txt
Unable to open 'blah.txt': No such file or directory
```

Podemos também concentrar tanto output quanto erros num único arquivo:

```bash
$ echo 'My message' > out.log 2>&1
```

O `&1` significa o stream atual para o file descriptor 1, neste caso o arquivo `out.log`.&#x20;

### Utilizando outros file descriptors

Com exceção dos *fd* padrão 0, 1, e 2, podemos criar outros file descriptors e utilizarmos como IPC? Sim, podemos! Vamos começar criando um diretório onde vamos armazenar estes fd temporários:

```bash
$ mkdir /tmp/fd
```

Vamos supor que queremos atribuir o `fd 42`. Devemos então preparar nosso fd, que será um canal de *comunicação de única via,* ou **single-communication channel** entre dois processos.&#x20;

Primeiro, abrimos o **fd** para para escrita `>` (STDOUT):

```bash
$ exec 42> /tmp/fd/42
```

Agora, redirecionamos uma mensagem para o *fd* utilizando **redirecionamento de stream** de escrit&#x61;**:**

```bash
$ echo 'Some message' >&42
```

Então com o comando **base64**, e sabendo que podemos ler de qualquer fd com redirecionamento de streams, fazemos o redirecionamento de *leitura:*

```bash
# Primeiro, abrir o fd para leitura < (STDIN)
$ exec 42< /tmp/fd/42 

# Executar o comando com o redirecionamento para o fd
$ base64 <&42
U29tZSBtZXNzYWdlCg==
```

File descriptors são recursos, portanto depois de utilizados, devem ser "liberados" no sistema operacional, ou seja, **fechados tanto para escrita quanto leitura**:

```bash
$ exec 42<&-
$ exec 42>&-
```

### Resumo

Nesta seção, vimos como diferentes processos se comunicam utilizando uma forma primitiva de IPC, que são os *file descriptors*.

Uma vez que entendemos isto, já podemos ir para a próxima etapa que irá explorar outra forma de comunicação um pouco mais sofisticada, **UNIX pipes**.


# UNIX Pipes

Para entendermos UNIX *pipes*, devemos primeiro recapitular o seguinte exemplo:

```bash
$ echo 'my precious' > rawcontent.txt
$ base64 < rawcontent.txt
bXkgcHJlY2lvdXMK
```

* O programa `echo` redireciona a o **output** para o arquivo `rawcontent.txt`
* O conteúdo do arquivo é enviado como **input** para o comando `base64`

Note o padrão aqui: temos uma *pipeline* de transformação de dados, onde o **output de um programa é utilizado como input** para o próximo programa.&#x20;

Esta técnica de pipeline é utilizada em sistemas UNIX-like com o operador *pipe* `|`.

## UNIX pipelines

Ao invés de escrevemos nossa pipeline em múltiplas linhas, dificultando a legibilidade caso a complexidade aumente, podemos utilizar o operador `|` para montar uma sentença única de comandos encadeados:

```bash
$ echo 'my precious' | base64
bXkgcHJlY2lvdXMK
```

Muito melhor, não? E muito provável você já viu isto em algum lugar, por exemplo:

```bash
$ ps ax | grep ruby
88327 s002  S+     0:00.94 docker run -it ruby irb
88330 s002  S+     0:00.92 /usr/local/bin/com.docker.cli run -it ruby irb
91074 s003  S+     0:00.00 grep ruby
```

O **output** do comando `ps` é enviado como **input** para o comando `grep`. *Cool, uh?* Este pipe é chamado de *pipe anônimo, ou* **anonymous pipe**!

### Anonymous pipe

Este pipe é chamado de **anônimo** justamente por não ter nome e ser temporário, pois o *fd* é criado durante a pipeline e depois é liberado.

Este tipo de **IPC** utiliza uma comunicação tem as seguintes características:

* **One-way**, ou seja, a informação trafega apenas e uma única direção
* **FIFO** (first-in, first-out), ou seja, o output é redirecionado para um pipe e **enfileirado** como input em outro pipe do próximo comando

```bash
$ ps ax | grep docker | tail -n 3

62374 s039  S+     0:05.31 /usr/local/bin/com.docker.cli run -it ubuntu bash
65442 s040  S+     0:02.93 docker run -it ubuntu bash
65445 s040  S+     0:02.86 /usr/local/bin/com.docker.cli run -it ubuntu bash
```

Quando um `|` é criado, abre-se um par de *file descriptors*, uma para escrita e outro para leitura, tal como fizemos na seção anterior com *custom fd*.

Como a pipe é anônima, ambos file descriptors abertos são utilizados apenas no contexto da pipeline e são automaticamente liberados/fechados quando a **pipeline** termina.

Apesar de pipes anônimas `|` serem utilizadas praticamente em quase tudo, é possível criamos pipes **com nomes**?

### Named pipes

Como o próprio nome diz, **named pipes** são pipes *com nomes*. São similares a pipes anônimas; empregam **FIFO** e são uma forma de IPC de via única (**one-way**).

A única diferença é que um named pipe é criado de forma explícita via comando `mkfifo`, onde um arquivo é criado no *filesystem* e aberto para escrita e leitura.

```bash
$ mkfifo myqueue
```

Um arquivo chamado `myqueue` é criado.&#x20;

Vamos enviar uma mensagem para o pipe com o comando `echo`, utilizando redirecionamento de stream `>` que vimos na última seção:

```bash
$ echo 'my precious' > myqueue 
```

Note que o processo fica bloqueado, à espera de algo.

#### IPC one-way

Por ser uma estrutura de fila *FIFO* simples e ser utilizado como *one-way IPC*, o sistema operacional precisa garantir que a mensagem **será recebida por outro processo.** Por isso o processo *escritor*, ou **writer**, fica bloqueado, pois é preciso que outro processo outro processo *leitor* (**reader**) para "consumir" a mensagem do pipe.&#x20;

Em outra sessão do bash, vamos consumir a informação do pipe utilizando o comando `cat`:

```bash
$ cat myqueue
my precious
```

Yay!&#x20;

O mesmo acontece se iniciarmos com o *leitor*: este fica bloqueado à espera que alguma mensagem chegue no pipe, no caso através de outro processo *escritor*.&#x20;

### Implementando um simples Background Job com UNIX pipes

Utilizando **anonymous pipes e named pipes**, podemos explorar a funcionalidade primitiva de um sistema de processamento assíncrono (**background job**).

Começamos por definir os componentes:

1. Um processo *leitor*, ou **consumer**, fica infinitamente à espera de mensagens no *pipe* (fila)
2. Diferentes *escritores*, ou **publishers**, colocam mensagens no pipe de forma assíncrona
3. O processo leitor (**consumer**) recebe cada mensagem no pipe e faz o devido **processamento da mensagem**

:point\_right: Nosso background job irá fazer a simples tarefa de receber uma mensagem **codificada** em *base64*, **decodificá-la**, e então mostrar no screen (STDOUT).

#### **Consumer**

Primeiro, criamos o pipe que representará a "fila" do nosso background job:

```bash
mkfifo myqueue
```

Agora, o consumer fica em **loop infinito** à espera de mensagens na fila.&#x20;

Dentro do *loop*, consome a mensagem, decodifica-a e então mostra no screen:

```bash
while true
do
  ## Fica bloqueado à espera da próxima mensagem na fila
  ENCODED=`cat myqueue` 
  
  ## Quando a mensagem chega na fila, decodifica-a utilizando o comando
  ##  echo e base64 com anonymous pipes
  DECODED=`echo $ENCODED | base64 -d`
  
  echo "Mensagem decodificada: $DECODED"
done
```

E o consumer está pronto. Código final do arquivo `consumer.sh`:

```bash
#!/bin/bash

## Cria o named pipe
mkfifo myqueue

echo 'Aguardando jobs na fila...'

while true
do
  ## Fica bloqueado à espera da próxima mensagem na fila
  ENCODED=`cat myqueue` 
  
  ## Quando a mensagem chega na fila, decodifica-a utilizando o comando
  ##  echo e base64 com anonymous pipes
  DECODED=`echo $ENCODED | base64 -d`
  
  echo "Mensagem decodificada: $DECODED"
done
```

Em uma sessão do bash:

```bash
$ bash consumer.sh
Aguardando jobs na fila...
```

E em outra sessão do bash, podemos utilizar vários *producers* para enviar diversas mensagens codificadas para a fila:

```bash
$ echo 'my precious' | base64 > myqueue
$ echo 'pipes are awesome' | base64 > myqueue
```

Consultando o output na sessão do consumer:

```bash
Mensagem decodificada: my precious
Mensagem decodificada: pipes are awesome
```

### Resumo

Que jornada! Nesta seção vimos a utilização de **UNIX pipes** para IPC, vimos a semelhança e diferença entre anonymous pipes `|` e named pipes, bem como a implementação de um **sistema de background jobs** com pipes.

Agora, é hora de explorar uma forma ainda mais sofisticada de IPC: **UNIX Sockets**.


# UNIX Sockets

Para entender o que é um socket, vamos lembrar dos **named pipes**, que são arquivos FIFO compartilhados *entre dois processos*.

### Named pipes são uni-direcionais

Um processo **reader** fica bloqueado na fila até que alguma mensagem de outro processo **writer** seja escrita no pipe.&#x20;

```bash
## Cria o arquivo FIFO
$ mkfifo queue.sock

## Reader
$ cat queue.sock

## Writer
$ echo PING > queue.sock
```

<figure><img src="/files/1yLxtpZEcNj4ysxefI1v" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/6w9Lt4c5FJCoDHooC2pa" alt=""><figcaption></figcaption></figure>

Como podemos ver no exemplo, *named pipes* são a princípio **uni-direcionais**, ou seja, a informação caminha em apenas uma direção. No nosso caso, do **writer** para o **reader**, e não o contrário.

Para que a comunicação seja bi-direcional, ou seja, que caminhe em ambas direções, *temos de criar 2 named pipes.*&#x20;

Vamos simular uma comunicação **request-response,** numa arquitetura também conhecida como **cliente-servido**&#x72;*:*

```bash
## Cria os named pipes FIFO
$ mkfifo req res

## Server
$ cat req; echo PONG > res

## Client
$ echo PING > req; cat res
```

<figure><img src="/files/Efxkn8J52XTvyxcS6vSl" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/80ZW0zA3MTtqHEsl2M4M" alt=""><figcaption></figcaption></figure>

Note acima que a informação caminha do **client** para o **server**, através do pipe `req`, e do server para o client através do pipe `res`. Este tipo de comunicação é *bi-direcional*, ou **full-duplex.**

E se, ao invés de pipes, pudéssemos utilizar uma estrutura nativa do *sistema operacional*, que faça uso de **file descriptors**, garanta comunicação **full-duplex** e possua **diversas formas de envio** de mensagens?

Sim, estamos falando de **sockets**.&#x20;

## UNIX Sockets

**Sockets** são estruturas de *networking* para comunicação full-duplex entre dois processos distintos (IPC). Para este guia, vamos focar em socket utilizado em ambientes *UNIX-like*, mais especificamente **Ubuntu.**

Começamos por instalar um pacote chamado `netcat`, que permite manipular sockets do sistema operacional:

```bash
$ apt install netcat
```

A API de UNIX sockets é bastante *versátil* e permite enviar diferentes tipos de mensagens para diferentes tipos de propósitos.&#x20;

Portanto, nas próximas seções iremos explorar os principais **tipos de sockets** e suas diferenças.


# Datagram sockets

Utilizando `netcat`, podemos criar um socket UNIX no formato **datagram**, que é um formato simples de mensagem, enviada em pedaços via sistema de arquivos.

```bash
## Server
$ nc -Uulv /tmp/queue.sock
Bound on /tmp/queue.sock
```

Vamos entender as opções do comando `nc`:

* `-U`: cria um UNIX socket
* `-u`: as mensagens no socket são do tipo datagram
* `-l`: atribui um file descriptor ao arquivo `queue.sock`
* `-v`: modo verboso, mostra quaisquer erros e outras saída no STDOUT

Com isto, o processo *server* está pronto a receber mensagens no socket. Agora, em **outra sessão**, enviamos mensagem para o socket, tal como fizemos com named pipes:

```bash
$ echo PING > /tmp/queue.sock
bash: /tmp/queue.sock: No such device or address
```

Uh, oh...*por quê este erro?*&#x20;

Quando o socket é iniciado, lhe é atribuído um *file descriptor* para envio de mensagens. Entretanto, como socket atua na camada de rede, é criado através de um *dispositivo*, ou **device**. Tal dispositivo está associado ao file descriptor, portanto não devemos enviar a mensagem para o arquivo do socket, mas para o **file descriptor** atribuído ao **processo que iniciou** o socket., ou seja, o **server.**&#x20;

Já sabemos como enviar redirecionar mensagem a um fd através de redirecionamento de streams, mas para o fd do reader precisamos saber o *identificador do processo*, ou **PID.**&#x20;

Com o comando `ps -C [program]`, conseguimos saber o processo que está rodando o programa `nc`, e com a opção `-o pid=`, é mostrado no output apenas o PID do processo:

```bash
$ ps -C nc -o pid=
1022
```

Okay, com o PID em mãos, onde está o *file descriptor*?&#x20;

Geralmente, em distribuição *Ubuntu*, o caminho para o **fd** associado ao STDIN do processo é `/proc/[pid]/fd/0`, lembrando que STDIN é sempre associado ao `fd 1` para qualquer processo UNIX.

```bash
$ echo PING > /proc/1022/fd/1
```

Um exemplo da funcionalidade completa:

<figure><img src="/files/ttK73cfiDc6SCCy7P47W" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/JgNBEwfcS5uRofLFlbsu" alt=""><figcaption></figcaption></figure>

#### Full-duplex

Okay, mas toda esta comunicação com socket ainda está uni-direcional, ou seja, a informação caminha do writer para o reader, e não faz o caminho inverso.&#x20;

<figure><img src="/files/L3PhI42PlkuwUABsfuN8" alt=""><figcaption></figcaption></figure>

Isto porque precisamos de outro arquivo de socket para receber a mensagem de volta do reader, tal como fizemos no exemplo com 2 named pipes (FIFO) para comunicação **bi-direcional.**

Hoje é nosso dia de sorte, o comando `nc` permite criar um arquivo de socket a partir de outro já existente:

```bash
$ nc -Uuv /tmp/queue.sock
Bound on /tmp/nc.XXXXPRdYP5
```

Note a mensagem `Bound on /tmp/nc.XXXXPRdYP5`, que significa que o programa `nc` criou outro socket para o client, associando-o ao socket do server. Desta forma, podemos ter uma comunicação **full-duplex** com UNIX sockets.

* **Client socket**: /tmp/nc.XXXXPRdYP5
* **Server socket**: /tmp/queue.sock

<figure><img src="/files/FIQ1KrRuGdVtOOluClQP" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/36C7l94VHXoaSI1dB84r" alt=""><figcaption></figcaption></figure>

### Considerações sobre datagram sockets

Há alguns pontos que devemos considerar quando utilizamos datagram sockets:

* não há garantia de que a mensagem chega na ordem correta, pode ser que pedaços da mensagem sejam enviados de forma desordenada
* não há garantia que todos os pedaços da mensagem sejam entregues de todo

Quando a consistência da mensagem importa, devemos utilizar outro tipo de socket, o **stream socket,** que será assunto da próxima seçã&#x6F;**.**


# Stream sockets

Com **streams**, o socket tem a mesma funcionalidade que *datagrams*, mas com a diferença que uma **conexão é criada** entre o pipe do *client* e o pipe do *server*.&#x20;

Nesta conexão, é implementado um **conjunto de procedimentos** de pergunta-resposta para **garantir** que a mensagem é entregue em sua *totalidade e na ordem correta*.

Com isto, podemos dizer que stream sockets utilizam uma conexão, ao passo que datagram sockets não utilizam uma conexão.&#x20;

<figure><img src="/files/vw5mWXKo83Qx1ulPtluP" alt=""><figcaption></figcaption></figure>

Por padrão, o comando `nc` trabalha com sockets de **conexão**, portanto para criarmos um socket stream basta **removermos** a opção `-u`, opção esta que utilizamos para criar *datagram* sockets na última seção.

```bash
## Server
$ nc -Ulv server.sock
Bound on server.sock
Listening on server.sock
```

Note que agora, vemos uma mensagem "Listening on server.sock", que significa que o server está **à espera de novas conexões** no socket.&#x20;

Para criar o client, o processo é o mesmo, **não utilizando** a opção `-u`:

```bash
$ nc -Uv server.sock
```

<figure><img src="/files/nmPCRjriQqIk1NITOqix" alt=""><figcaption></figcaption></figure>

### Resumo

Até aqui, conseguimos entender **UNIX sockets** e a diferença entre *datagram* e *stream*.&#x20;

*So far so good*, mas os processos estão sempre no mesmo computador. Indo um pouco mais além, seria possível que **2 processos isolados em computadores distintos** se comunicassem?

Sim, caso os computadores estejam conectados na mesma **rede local** (LAN) ou em uma *rede global de computadores* (**Internet**).&#x20;

<figure><img src="/files/QnakdLXrPePNgbciqCHs" alt=""><figcaption></figcaption></figure>

Um UNIX socket por si só **não resolve IPC** entre 2 processos em computadores distintos conectados numa rede local ou global.

Vamos, então, explorar outras opções de IPC para processos em computadores distintos nas próximas seções deste guia.


# Internet

Antes de seguirmos com opções de comunicação (IPC) para processos em computadores distintos, vamos entender um pouco o contexto da **Internet** e suas formas de comunicação.

### Um pouco de história da Internet

O conceito de internet, como conhecemos, foi criado na década de 60 com o intuito de resolver um problema de interconexão entre computadores em redes distintas.&#x20;

O objetivo era criar infra-estrutura para que a informação trafegasse rapidamente de um ponto remoto a outro no planeta, **de forma descentralizada**.

Durante o desenvolvimento do projeto, para evitar que cada rede tivesse sua própria forma de enviar mensagens, foi preciso delinear convenções e protocolos de comunicação dentro desta rede.&#x20;

Dentre diversos outros padrões e conjuntos de protocolos criados naquela época de *Protocol Wars*, cria-se também o **TCP/IP**, que acaba se tornando a *principal* suite de protocolos para a internet desde então.

### &#x20;TCP/IP

Um dos conjuntos de padrões para a internet que foi concebido e hoje amplamente utilizado é a Suite de *Protocolo de Internet*, ou **Internet Protocol Suite TCP/IP**, que define protocolos de comunicação entre diferentes computadores na rede global.&#x20;

No TCP/IP, são estabelecidas **4 camadas de comunicação**:

* **Application**: é o formato de mensagem que os diferentes programas irão interpretar. Exemplos como FTP, SSH e HTTP aparecem nesta camada.
* **Transport**: nesta camada, são definidas regras de como a informação será transportada. Se será sem conexão, como os datagram sockets, ou com conexão, como os stream sockets, dentre outras regras. Exemplos como TCP e UDP surgem nesta camada.
* **Internet**: cada processo na rede global precisa ter um identificador único, também chamado de *endereço de protocolo de internet*, ou **endereço de IP**. É esta camada que também define o formato da mensagem, se contém header, payload, dentre outros metadados.&#x20;
* **Link**: camada mais voltada para a parte de *roteamento*, especificações de canais de dados, dentre outros protocolos para a parte **física** da comunicação.

<figure><img src="/files/sSYcLgjCJJ6jzb5aT7YA" alt=""><figcaption></figcaption></figure>

Neste guia, para que possamos entender os fundamentos da Web, vamos focar na camada de Transporte e Aplicação, pois a camada de Internet é basicamente a definição do que temos por endereçamento IP e camada de Link refere-se a toda a parte física (WiFi, fibra ótica, rádio, etc).&#x20;

### Camada de Transporte

Uma vez que a informação trafegou pela Internet e chegou ao computador, como a mesma é enviada ao processo destinatário?

#### Sockets

Sabemos que *sockets* são utilizados como IPC dentro do sistema operacional. Com isto, no TCP/IP, uma solução seria **implementar a API de Sockets**, assim conseguiríamos manter a mesma *interface*, facilitando a evolução de sistemas construídos em cima da Internet.&#x20;

É com esta ideia que foi criado o projeto **Berkeley Sockets**, que é uma API que faz parte do *TCP/IP* mas que implementa *UNIX Sockets*. Desta forma, com Berkeley Sockets, conseguimos IPC de forma muito similar à UNIX Sockets.&#x20;

Tipos de sockets no TCP/IP:

* **TCP**: é a utilização de stream sockets no TCP/IP
* **UDP**: é a utilização de datagram sockets no TCP/IP

<figure><img src="/files/i9FlK6sf6rmGBrbTwpsS" alt=""><figcaption></figcaption></figure>

Nas próximas seções, vamos explorar TCP e UDP como formas de comunicação entre processos através de TCP/IP.


# TCP Sockets

**TCP**, ou *Transmission Control Protocol*, é uma abstração de **stream sockets** na camada TCP/IP da Internet. Por se no formato stream, há a garantia de entrega da mensagem na *ordem correta*.

Assim como UNIX sockets, também podemos criar TCP sockets com `netcat`. Inclusive o padrão do netcat é TCP, portanto basta removermos a opção `-U` dos exemplos que criamos anteriormente e pronto, temos um socket TCP.

<pre class="language-bash"><code class="lang-bash"><strong>## Server
</strong><strong>$ nc -lv /tmp/server.sock
</strong><strong>nc: getaddrinfo: Servname not supported for ai_socktype
</strong></code></pre>

Uh, oh...não conseguimos criar o server no socket `server.sock` com TCP.&#x20;

Para entender este erro, vamos primeiro simular como seria um **cliente remoto**, ou seja, em outro computador, conectando neste socket.&#x20;

*Ps: para efeitos didáticos, vamos fazer todos os exemplos no mesmo computador. Com TCP funciona na mesma, é como se tivéssemos em computadores diferentes.*

```bash
## Client
$ nc -v /tmp/server.sock
nc: missing port number
```

Vamos por ora *ignorar* a mensagem de erro. Repare o problema neste comando. O cliente precisa **saber o caminho do socket no servidor** remoto. Isto pode trazer *problemas de segurança* graves, pois um cliente conhecer o caminho do socket **abre caminhos para possíveis ataques** ao servidor.

<figure><img src="/files/SNR6aaQu10C7W2xdstIc" alt=""><figcaption></figcaption></figure>

Por isto é **obrigatório** que o client informe o *número da porta*. Mas que porta? O quê é uma porta neste contexto de TCP/networking?

### Portas de rede

**Porta** é um conceito em redes de computadores que permite abstrair o caminho de um socket, mais precisamente de um **processo em execução no sistema operacional**.&#x20;

A forma que uma porta é associada e um processo é através de um conceito parecido com *file descriptors*, que são **socket descriptors**.

<figure><img src="/files/RWUNldb4eAdf6740ac0Q" alt=""><figcaption></figcaption></figure>

### Conexão cliente-servidor

Como agora sabemos que um socket TCP precisa do número da porta, basta iniciarmos o comando `netcat` com o número de uma porta que não esteja sendo utilizada, ao invés do nome do socket.

```bash
## Server
$ nc -lv 8080
Listening on 0.0.0.0 8080
```

* `-l`: o server ficará à escuta de conexões na porta 8080
* `-v`: modo verboso

Nice! Temos um TCP socket rodando na porta *8080* do computador. E como podemos confirmar isto? Com o comando `lsof`:

```bash
$ lsof -i tcp:3000
COMMAND  PID USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
nc      1113 root    3u  IPv4 7228577      0t0  TCP *:8080 (LISTEN)
```

Podemos ver que o comando `nc`, de PID `1113`, está no modo **LISTEN TCP** na porta **8080**.&#x20;

Agora, vamos ao lado do cliente:

```bash
## Client
$ nc -v localhost 8080
Connection to localhost (127.0.0.1) 8080 port [tcp/*] succeeded!
```

Note que o comando `nc`, caso seja utilizado como client, precisa informar **2 argumentos**:

1. endereço do server, neste caso, utilizamos o próprio **localhost** mas poderia ser qualquer IP público da rede
2. número da porta onde o server está à escuta, neste caso, **8080**

<figure><img src="/files/WFbN7c7zTwJ9wrnj2QWe" alt=""><figcaption></figcaption></figure>

Na interação acima, repare que assim que o cliente conecta no server, é mostrada a mensagem `Connection received on localhost 46216`.&#x20;

Isto porque, pela característica **full-duplex** como vimos com 2 named pipes e UNIX sockets, os sockets TCP também estabelecem *outro socket* para leitura do cliente, neste caso, **outra porta de rede**.&#x20;

Inclusive dá pra confirmar verificando todos os processos interagindo com a porta 8080:

```bash
$ lsof -i tcp:8080
COMMAND  PID USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
nc      1280 root    3u  IPv4 7280841      0t0  TCP *:8080 (LISTEN)
nc      1280 root    4u  IPv4 7280100      0t0  TCP localhost:8080->localhost:46216 (ESTABLISHED)
nc      1283 root    3u  IPv4 7280954      0t0  TCP localhost:46216->localhost:8080 (ESTABLISHED)
```

Com isto, podemos concluir que:

* há um *file descriptor* (3u) criado para o socket do server e também as mensagens dentro da conexão TCP que o cliente envia para o server, **da porta 46216 para a porta 8080**
* há outro *file descriptor* (4u) para representar as mensagens dentro da conexão TCP que o server envia de volta para o cliente, **da porta 8080 para a porta 46216**

É tudo sobre **file descriptors**!

### Resumo

Esta seção apresentou a funcionalidade de **TCP sockets**, bem como sua similaridade com UNIX *stream* sockets.&#x20;

A seguir, vamos explorar como trabalhar com sockets de internet que utilizam *datagram*, os **sockets UDP**.


# UDP Sockets

Assim como TCP socket, **UDP**, ou *User Datagram Protocol*, também precisa estar associado a uma porta de rede. E igualmente TCP, UDP também é full-duplex.

Por outro lado, UDP não estabelece uma conexão, ou seja, não utiliza *stream sockets*, mas **datagram sockets,** e portanto não tem as garantias de entrega do TCP.

Vamos criar um server UDP, lembrando que precisamos indicar a opção `-u` para que seja um socket **datagram**.

```bash
## Server
$ nc -ulv 8080
Bound on 0.0.0.0 8080
```

E do lado do cliente, indicando o endereço do server (localhost) e porta de rede:

```bash
## Client
$ nc -uv localhost 8080
Connection to localhost (127.0.0.1) 8080 port [udp/*] succeeded!
```

Confirmando com `lsof` as portas utilizadas:

```bash
$ lsof -i udp:8080
COMMAND  PID USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
nc      1325 root    3u  IPv4 7313683      0t0  UDP localhost:8080->localhost:59986
nc      1328 root    3u  IPv4 7313715      0t0  UDP localhost:59986->localhost:8080
```

* um processo para representar as mensagens escritas do server para o client
* outro processo para representar as mensagens escritas do client para o server

### Resumo

É isto. Esta seção teve por objetivo explicar e explorar utilização de **UDP sockets**.&#x20;

Nas próximas seções, vamos continuar com o objetivo inicial deste guia, e começar a *construir um Web server* aplicando estes conceitos, com o intuito de demonstrar os fundamentos da Web.


# Pseudo devices

Uma forma interessante de enviar mensagens para um socket *TCP* ou *UDP* quando não se tem um cliente como `netcat` ou `socat`, é utilizando os **pseudo-devices.**

Os pseudo-devices são criados no **filesystem** e localizados em `/dev`.

```bash
$ ls /dev
console  fd    mqueue  ptmx  random  stderr  stdout  urandom
core     full  null    pts   shm     stdin   tty     zero
```

Quando um TCP server é criado, podemos **enviar uma mensagem** a ele através de pseudo-devices, utilizando a mesma técnica de *redirecionamento de streams* que vimos com file descriptors e standard streams:

```bash
## Server
$ nc -lv 8080
Listening on 0.0.0.0 8080
```

O caminho padrão para um pseudo-device é:

> ```
> /dev/[protocol]/localhost/[port]
> ```

Então enviamos a mensagem sem a necessidade de `netcat`, apenas com **pseudo-devices**:

```bash
$ echo PING > /dev/tcp/localhost/8080
```

<figure><img src="/files/Dobio77BAvCdd5KklLaB" alt=""><figcaption></figcaption></figure>

Note que assim que o server recebeu a mensagem, a **conexão com o cliente foi encerrada** e o processo do s**erver foi terminado**.

Se quisermos que o processo do server se mantenha em execução mesmo quando uma conexão de um cliente encerra, temos que indicar a opção `-k`:

```bash
$ nc -lvk 8080
```

<figure><img src="/files/MjNXC4rWUMUsUY4vrRNf" alt=""><figcaption></figcaption></figure>

Reforçando que, conforme exemplo acima, cada nova conexão é atribuída a uma porta de rede diferente, caracterizando a capacidade **full-duplex** do TCP entre diferentes clientes.


# Breve história da Web

### Anos 90, o embrião da Web

No final dos anos 80, *Tim Berners-Lee*, um developer da **CERN**, uma agência europeia de pesquisa nuclear, decide explorar um tipo de documento em uma arquitetura cliente-servidor TCP/IP da época.&#x20;

A este documento, é trazida uma ideia até antiga da década de 30, de conter textos que podem conter apontamentos para outros textos, através de links. Esta capacidade de fazer link de um texto a outro é chamada de hyperlink, e o texto que contêm **hyperlinks** é chamado de **hypertexto**.&#x20;

O objetivo desta exploração era que esses documentos pudessem conter informação pública, através de uma rede global de computadores descentralizada, favorecendo a pesquisa e partilha de conhecimento.

<figure><img src="/files/gnOK5GggYoAEFZPTInUH" alt=""><figcaption></figcaption></figure>

Buscava-se então uma forma de **padronizar** a comunicação destes documentos através da internet, estabelecendo **protocolos** e convenções para que diferentes aplicações pudessem utilizar a mesma *interface*.&#x20;

### HTTP

Com isto em mente, foi estabelecido um conjunto de regras, através de um **protocolo** bem definido:

* era necessário que a informação trafegasse pela internet, através de **TCP/IP**
* era também preciso garantir a entrega da mensagem na ordem, portanto para transporte, seria necessário utilizar o protocolo **TCP**
* a comunicação teria 2 tipos de mensagens: **request e response**
* a mensagem iria ter um padrão rigoroso de formato, com **introdução**, **cabeçalhos** e **corpo** (conteúdo)
* o conteúdo da mensagem precisaria ser **hypertexto**
* por ser um protocolo por cima do TCP, no conjunto TCP/IP este protocolo estaria na camada de **Aplicação**

Este protocolo tem um nome, *Hypertext Transfer Protocol*, ou simplesmente **HTTP**. Nasce, então, a **World Wide Web**.

### Nexus, o primeiro Web browser

O primeiro web browser criado por Tim Berners-Lee e que deu início à Web era chamado WorldWideWeb, mas posteriormente foi renomeado para Nexus.&#x20;

<figure><img src="/files/e7XgLYpBMXOxrooSgDwP" alt=""><figcaption></figcaption></figure>

### HTML, a linguagem da Web

O tipo de conteúdo na mensagem HTTP precisava ter alguma garantia semântica, ou seja, os elementos da página no web browser tinham de serem *marcados* para que o browser pudesse colocar a formatação devida.&#x20;

Com isto, como parte do projeto, surge o **HTML**, ou *Hypertext Markup Language*, que é uma linguagem de marcação para documentos Web.

Mas os elementos HTML não definem *apresentação*, posicionamento e estilo. Para isto é necessário uma ferramenta adicional ao HTML, para que os elementos sejam apresentados de forma adequada e que a plataforma Web pudesse evoluir ao longo do tempo.

### CSS

*Cascading Stylesheet*, ou **CSS**, é uma linguagem de stylesheet utilizada no projeto para que os elementos HTML possam ser **apresentados** de acordo com a necessidade do website.&#x20;

Com isto, os elementos podem ser *posicionados* à esquerda, ao topo, terem cores variadas etc, dando assim uma característica mais amigável para usuários da Web.

HTML e CSS foi um grande avanço para a partilha de documentos hypertexto, mas as páginas eram estáticas e não traziam funcionalidades dinâmicas.

### Batalha dos Web browsers

Esta época foi marcada pela **batalha** de web browsers.&#x20;

Um notório browser, chamado **Mosaic**, possuía *interface gráfica* e permitia renderização de imagens no HTML e submissão de formulários.&#x20;

Os developers do Mosaic, atuando na *Netscape*, criaram depois o **Navigator**, que tinha uma melhor aparência que o Mosaic, o que o fez se tornar o principal web browser na época.

Com a necessidade de trazer mais *dinamismo* para as páginas Web, os developers do Navigator decidiram criar rapidamente um *protótipo* de uma linguagem de programação para a web. Temos, então a linguagem **LiveScript**, que pouco tempo depois foi renomeada para **Javascript**.&#x20;

Em paralelo, a **Microsoft** entrou na onda e criou o **Internet Explorer**, e após fazer uma engenharia reversa do *Navigator*, criaram a própria linguagem de programação, chamada de **JScript**.&#x20;

Esta batalha entre **IE e Navigator** deixou os developers da Web com o trabalho de terem que optar por suportar um dos browsers, já que utilizavam motores de renderização diferentes, bem como uma linguagem de programação diferente. No pior dos casos, developers tinham de suportar ambos browsers.&#x20;

Era preciso padronizar.

### ECMAScript

*European Computer Manufacturers Association*, ou **ECMA**, é uma organização sem fins-lucrativos criada nos anos 60 que tem por intuito criar padrões e estabelecer convenções de nomes para serviços de comunicações, engenharia e tecnologia.&#x20;

Em 1997, Netscape submete Javascript à ECMA, pelo que é aceito e ganha o nome *ECMA-262*, ou **ECMAScript**.

Apesar da Netscape ter padronizado o ECMAScript, a Microsoft conseguiu uma grande fatia do mercado e dominou o mercado web até 2004.&#x20;

### 2004, Mozilla e Firefox

Em 2004 a **Mozilla**, sucessora do *Netscape*, lança o **Firefox**, que consegue buscar uma grande parte do mercado.&#x20;

Um ano mais tarde, em 2005, Mozilla se junta à ECMA International e começa a dar mais atenção ao desenvolvimento do ECMAScript, que estava estagnado.&#x20;

### Fim da batalha dos Web browsers

Conforme os anos foram se passando, o trabalho feito na especificação do ECMAScript foi evoluindo, passando por ECMAScript 4 (ou **ES4**), depois **ES5** e assim por diante. E com isso, a Web foi ficando mais padronizada, encerrando uma década de batalha de browsers.&#x20;

**Google** também trouxe em meados dos anos 2000 o **Chrome**, contando com um runtime sofisticado com Just-In-Time compilation (*JIT*), chamado de **V8,** e que interpreta ECMAScript.

Não obstante, o *IE* chegou ao fim mas a Microsoft continuou desenvolvimento criando o **Edge**, um web browser que utiliza um motor baseado no Google Chrome, o **Chromium**.

### Desafios com interfaces ricas

A Web estava mais dinâmica mas faltava uma melhor interação entre browsers e servers, mas a exigência de *interfaces ricas*, ou **rich interfaces**, fez com que novos desenvolvimentos em volta da Web fossem feitos.

A página era servida e, a cada vez que o usuário precisava de novas informações na mesma página, era feito outro pedido ao server, causando um recarregamento completo da página novamente. Isto trazia alguns problemas de usabilidade e a Web precisava evoluir.

### XHR

Em paralelo, havia uma API criada pela Microsoft nos anos 2000 chamada *XMLHttpRequest***,** ou **XHR**, que permitia fazer chamadas assíncronas ao servidor, trazendo como resposta uma informação com formato *XML*, que posteriormente foi incorporado também suporte a *JSON* e outros formatos.

### Os primeiros frameworks JS

Ainda em meados dos anos 2000, *Jesse James Garrett*, um web designer e desenvolvedor web, traz a ideia de um conjunto de ferramentas em volta da Web que permitem que o browser possa fazer um pedido assíncrono ao server e renderizar apenas uma parte da página, melhorando a usabilidade dos usuários de websites.

Então, esta ideia busca unificar Javascript e XHR para resolver o problema de carregamento completo da página.&#x20;

Surge então o *Asynchronous Javascript and XML*, ou **AJAX**.

Após isto, o mercado e comunidade web começam a trabalhar em ferramentas para abstrair complexidade em volta do AJAX e assim, permitir uma melhor experiência ao developer (DX) bem como compatibilidade com diversos browsers à medida que ECMAScript ia sendo evoluída.

Criam-se então ferramentas que abstraem complexidade, também chamadas de **bibliotecas ou frameworks**, sendo as pioneiras JQuery, Prototype a MooTools.

### Anos 2010, a maturidade da Web

Por volta de 2010, o desenvolvimento do ECMAScript vai amadurecendo, trazendo compatibilidade com diversos browsers bem como uma API mais rica e versátil. Assim, surge o **ES6**, que revoluciona a indústria Web.

### Single Page Applications

Apesar da maturidade do *ES6*, a indústria fez um movimento em direção à ideia de, ao invés de carregar páginas à medida que o usuário clica nos links, carregar todo o site (páginas HTML, CSS e Javascript) no primeiro request, trazendo para o usuário uma experiência "nativa" no web browser, como se estivesse navegando em uma aplicação sem carregamento de páginas.&#x20;

Isto foi possível graças ao AJAX.

Então, frameworks SPA como Backbone são criados. Mais tarde, Ember, AngularJS, React, Vue etc. **Bom, o resto é história.**


# Construindo um simples echo Web server

Agora que conhecemos o contexto por trás da Web e suas principais tecnologias envolvidas, vamos iniciar com a construção de um Web server simples que response a mensagem `PONG`, para que possamos colocar em prática este conhecimento.

Nas próximas seções, vamos passo-a-passo explorando os conceitos necessários para esta construção, começando pelo HTTP, que é tema da próxima seção.


# Entendendo o HTTP

Primeiro, vamos entender o formato da mensagem HTTP. Basicamente, existem 2 tipos de mensagens neste protocolo:

* **Request**, que representa a mensagem enviada do cliente para o servidor
* **Response**, que representa a mensagem, ou resposta, enviada do servidor para o cliente

### Request

A seguir descrevemos o formato padrão de uma mensagem HTTP Request:

```
GET /index.html HTTP/1.1
Content-Type: plain/text

PING
```

1. a primeira linha, conhecida como headline, define o método ou verbo, em seguida o caminho para o recurso, e por último a versão do protocolo.
2. (opcional) a segunda linha define o cabeçalho, ou header. Os headers no HTTP são opcionais e representam metadados que podem ser lidos tanto pelo server quanto pelo client.
3. a terceira linha é uma linha em branco, mandatória para separação entre headers e payload
4. (opcional) a última linha refere-se ao payload, ou body, que representa o conteúdo principal a ser enviado na mensagem HTTP. O body é opcional.

### Response

E agora demonstramos o formato do HTTP Response:

```
HTTP/1.1 200
Content-Type: plain/text
Content-Length: 4

PONG
```

1. a primeira linha, conhecida como headline, define o versão, seguida do status code, que representa um código de sucesso ou falha.
2. (opcional) a segunda linha define o cabeçalho, ou header. Os headers no HTTP são opcionais e representam metadados que podem ser lidos tanto pelo server quanto pelo client.
3. a terceira linha é uma linha em branco, mandatória para separação entre headers e payload
4. (opcional) a última linha refere-se ao payload, ou body, que representa o conteúdo principal a ser enviado na mensagem HTTP. O body é opcional.

Vamos agora entender mais alguns conceitos do HTTP.

### Versão

A versão do protocolo vai depender em como o server e client suportam, mas a primeira versão de 1989 era **HTTP/0.9.**

Anos mais tarde, o protocolo foi sofrendo melhorias, subindo para a versão **1.0**.

Em 1997 foi lançada a versão **HTTP/1.1** com diversas melhorias, o que predominou em todos os web browsers até 2015.

A versão **HTTP/2** é publicada em 2015 com suporte a uma web mais semântica, e hoje é utilizada em 45% dos websites e suportada por 96% dos web browsers.

Então em 2022 é publicada a versão **HTTP/3**, que traz notórias mudanças, dentre elas a utilização de outro protocolo de transporte, o **QUIC**, lançado em 2012 e que utiliza UDP com uma implementação de conexão leve e tem menos latência que TCP. HTTP/3 é utilizado por 24% dos websites.

Em testes de performance, HTTP/3 se mostrou mais rápido que HTTP/2 e até 3x mais rápido que HTTP/1.1.

Para fins didáticos neste guia, vamos utilizar **HTTP/1.1**, pois ainda assim é utilizado por mais de 80% dos websites.&#x20;

### Status code

Status code é um indicativo de sucesso, falha no servidor ou até mesmo falha na mensagem do cliente.&#x20;

Operações de sucesso costumam ficar na família **200, ou 2xx** de status codes.

Se o servidor não conseguir interpretar a mensagem do cliente por algum motivo ou validação de dados, é caracterizado um *erro de cliente*, ou **client error**. Client errors costumam ficar na família **400, ou 4xx** de status codes.

Se o servidor não conseguir processar a resposta por algum erro interno, seja o erro esperado ou não, é caracterizado um **server error**. Server errors costumam ficar na família **500, ou 5xx** de status codes.

Há também outra família de status codes, que é a **300 ou 3xx,** que indica que o aquele caminho específico não tem o recurso mas que o recurso foi modificado/redirecionado para outro caminho no servidor.

### Body&#x20;

O corpo da mensagem é o conteúdo principal. No HTTP, pode ser desde um simples texto, a um hypertexto, um arquivo PDF, uma imagem, JSON, XML etc, qualquer formato de conteúdo, desde que especificado nos metadados (headers) da mensagem.

Ou seja, embora foi concebido para, o conteúdo do HTTP não precisa ser necessariamente hypertexto (HTML).

### **HTTP headers**

O HTTP é um protocolo bastante versátil, e não à toa, se tornou o padrão para a Web. Esta versatilidade não seria possível sem o uso de metadados, ou headers, para que servidor e cliente possam trocar informação necessária sobre tipo do conteúdo, tamanho, regras de segurança, cache de dados entre outras informações extremamente relevantes.


# Primeira versão do server

Uma vez que sabemos o formato da mensagem HTTP, podemos voltar à utilização do `netcat`, que foi muito útil para entendermos sobre Sockets, para darmos início ao web server.

Vamos então iniciar o server:

```bash
## Server
$ nc -lv 8080
Listening on 0.0.0.0 8080
```

Em seguida, em outra sessão, iniciamos o client:

```bash
## Client
$ nc -v localhost 8080
Connection to localhost (127.0.0.1) 3000 port [tcp/*] succeeded!
```

No lado client, digitamos o HTTP request. E do lado server, digitamos o HTTP response.

<figure><img src="/files/4o0Fb08D2aKRdusLp0HJ" alt=""><figcaption></figcaption></figure>

Yay! *Nossa primeira mensagem HTTP* através de TCP sockets!&#x20;

Repare que o client escreve a mensagem a partir da interface interativa STDIN do client, pelo que também o server escreve a mensagem a partir da interface interativa STDIN do server.

É um pouco trabalhoso escrever a mensagem direto no STDIN, não? E se pudéssemos automatizar isto, fazendo com que a mensagem seja enviada com o comando `echo` por exemplo?

### Redirecionando streams

Sim, redirecionamento de streams nos ajuda a resolver este problema. Assim, o **output** do comando `echo` passa a ser o **input** do comando `netcat`, assim nosso server pode ser iniciado da seguinte forma:

```bash
## Server
$ echo -e "HTTP/1.1 200\r\nContent-Type: application/text\r\n\r\n\PONG" | nc -lv 8080
Listening on 0.0.0.0 8080
```

Ou melhor, com quebra de linhas utilizando backslash `\`:

```bash
## Server
$ echo -e "HTTP/1.1 200\r\n\
Content-Type: application/text\r\n\
\r\n\
PONG" | nc -lv 8080

Listening on 0.0.0.0 8080
```

Okay, entendemos o formato da mensagem HTTP, mas o que é esse tal de `\r\n`?

#### Line feed

Quando qualquer mensagem é lida no socket, esta chega como uma string literal sem quebra de linhas por padrão. Se quisermos indicar que entre `HTTP/1.1 200` e `Content-Type: application/text` haja uma quebra de linha como manda o protocolo, temos que indicar para o programa que lê do socket (neste caso o `netcat`) que é necessário quebrar a linha.

Por padrão, `\n` significa quebra de linha. Mas queremos que o programa leia a próxima linha a partir do primeiro caracter mais à esquerda, ou seja, do início. E por isto, é necessário prefixar o `\r`.&#x20;

Portanto, `\r\n` significa alimentação de linha, ou **line feed**.

#### E como fica o request?

<pre class="language-bash"><code class="lang-bash"><strong>## Client
</strong><strong>$ echo -e "GET / HTTP/1.1\r\n\
</strong>Content-Type: application/text\r\n\
\r\n\
PING" | nc -v localhost 8080

</code></pre>

Nice!

Note que, após enviar a mensagem de volta, o server não encerrou a conexão, fazendo com que o client ficasse bloqueado. Para mitigar isto por agora, podemos utilizar a opção `-N` no comando, que indica ao server para terminar quando a mensagem chegar ao fim.

```bash
## Server
$ echo -e "HTTP/1.1 200\r\n\
Content-Type: application/text\r\n\
\r\n\
PONG" | nc -lvN 8080

Listening on 0.0.0.0 8080
```


# Adicionando HTML

Na última seção construímos a primeira versão do nosso simples HTTP server, que envia uma mensagem `PONG` em plain text.&#x20;

Mas como estamos falando de Web, seria mais adequado enviarmos HTML no body, não?&#x20;

Vamos modificar o server para responder um **content-type header** `text/html`, indicando que o client deve saber *renderizar HTML*, bem como fazendo com que o body seja um **parágrafo** HTML, no caso colocando a palavra **PONG** dentro da HTML tag `<p>`.

```bash
## Server
$ echo -e "HTTP/1.1 200\r\n\
Content-Type: text/html\r\n\
\r\n\
<p>PONG</p>" | nc -lvN 8080

Listening on 0.0.0.0 8080
```

Do lado do client, não precisamos mais de body. Podemos, por exemplo, indicar que queremos uma rota/caminho para o recurso `/ping`, deixando assim nossa comunicação HTTP mais semântica.&#x20;

```bash
## Client
$ echo -e "GET /ping HTTP/1.1\r\n\
Content-Type: text/html\r\n\
\r\n" | nc -v localhost 8080

HTTP/1.1 200
Content-Type: text/html

<p>Hello</p>
```

Podemos ver que, apesar do HTTP header indicar que o tipo do conteúdo é HTML, o comando `netcat` do lado do client não sabe renderizar.&#x20;

Isto porque, para renderizar HTML, é preciso ter um motor de renderização, coisa que os Web browsers implementam. Vamos experimentar outros HTTP clients.

### Outros HTTP clients

Vamos começar com `curl`, que é um HTTP client muito utilizado para testes e automação na linha de comando do terminal:

```bash
## Client
$ curl localhost:8080/ping
<p>Hello</p>
```

Interessante. Podemos ver que o programa `curl` sabe **interpretar uma mensagem HTTP**, mas *não sabe renderizar HTML* pois não é um Web browser.

Então é hora de testar no **browser**.

<figure><img src="/files/cAskzwwsFw8ZVJ26Blu1" alt=""><figcaption></figcaption></figure>

**Super** **Yay**! Por isto é chamado de **Web browser**, correto? Vamos também olhar o request que o browser mandou para o server:

```
GET / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8
Sec-GPC: 1
Accept-Language: en-GB,en
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
```

Podemos reparar que a mensagem HTTP request tem o mesmo padrão que estávamos utilizando, com a diferença que o web browser adiciona muito mais HTTP headers para um melhor controle entre client e server.

Basta um web server saber ler e lidar com tais HTTP headers, enviar outros headers relevantes de volta no response e pronto, temos uma melhor experiência de utilização para os usuários de websites, tudo contemplado no padrão HTTP!

### Estado da conexão no HTTP

Importante destacar que, até o momento, tudo o que temos visto foi uma mensagem caminhar do cliente para o servidor (request), outra mensagem caminhar do servidor para o client (response) e então o server terminar o processo.&#x20;

O quê está acontecendo com a conexão?

#### HTTP é stateless por padrão

Como HTTP se posiciona na camada de Aplicação do TCP/IP, a informação que cliente e servidor trocam está sendo transportada via TCP.&#x20;

Em cada comunicação request-response é criada uma nova conexão TCP. Com isso, quando um cliente faz um pedido HTTP qualquer ao servidor e este responde, a conexão é encerrada e não há qualquer estado HTTP entre diferentes conexões TCP.&#x20;

Podemos então resumir o fluxo:

1. Client inicia conexão TCP como server
2. Client envia um request HTTP através da conexão
3. Server recebe a mensagem, processa e envia uma resposta HTTP
4. Server encerra a conexão com o client

Entender este fluxo é importante para compreendermos as limitações do HTTP por ser **stateless** (sem estado). Em futuras seções, vamos explorar formas de mitigar esta limitação para que possamos simular uma característica *stateful* (com estado).


# Construindo um Web server com Login & Logout

Na última seção vimos como construir um web server simples. Passamos por fundamentos e anatomia de uma mensagem HTTP.

Como próximo passo, vamos construir um web server completo, com funcionalidade de Login e também Logout. Desta forma poderemos compreender mais algumas partes fundamentais da Web.&#x20;

> Para os mais ansiosos, segue [este link](https://gist.github.com/leandronsp/3a81e488b792235b2be73f8def2f51e6) para o Gist com o código completo de um web server com Login & Logout em bash. Já para quem quer entender como funciona esse tipo de sistema na web, continue acompanhando o guia até ao fim.

### Provendo uma resposta dinâmica

Até agora, temos visto uma representação estática de resposta do server:

```bash
$ echo -e 'HTTP/1.1 200\r\n\r\n\r\n<h1>PONG</h1>' | nc -lvN 8080

Listening on 0.0.0.0 8080
```

Não importando qual request será enviado, o response será sempre o mesmo:

```bash
$ curl http://localhost:8080/
<h1>PONG</h1>

$ curl http://localhost:8080/users
<h1>PONG</h1>

$ curl -X POST http://localhost:8080/login -d name=Leandro
<h1>PONG</h1>
```

Entretanto, precisamos de uma resposta dinâmica, onde, dependendo do caminho da URL no request, o server poderá devolver uma mensagem diferente. Mas como fazemos isto?&#x20;

Para chegarmos a uma solução, vamos entender o mecanismo de redirecionamentos de streams com o comando `netcat`.

#### O comando netcat fica bloqueado à espera de mensagens no socket

Assim que o comando `nc` é iniciado, um socket é criado na porta escolhida e portanto fica à espera de mensagens em novas conexões criadas no socket.

#### O input (STDIN) do comando  é utilizado posteriormente como resposta HTTP no socket

Qualquer input passado ao comando `nc`, seja via interface STDIN ou redirecionado com UNIX pipe, é enviado posteriormente como resposta.

#### Qualquer request HTTP é enviado para o output (STDOUT) do comando

Quando uma mensagem chega em conexão no socket, esta é enviada para o STDOUT, ou pode ser redirecionada com UNIX pipe.&#x20;

```bash
## 1. Server fica bloqueado à espera de requests HTTP no socket
## 2. Quando um request HTTP chega, este é enviado para o STDOUT
## 3. O server agora fica bloqueado à espera de mensagem no STDIN, para ser 
##    enviada como HTTP response no socket
## 4. O response HTTP é enviado e a conexão com o client é encerrada
$ nc -lvN 8080
Listening on 0.0.0.0 8080
```

Em resumo, STDIN do `nc` é enviado como HTTP response. E requests HTTP são enviados para o STDOUT.&#x20;

Sabendo disto, podemos utilizar UNIX pipes para criar uma pipeline de request-response dinâmica, assim deixamos nosso server um pouco mais sofisticado pronto a receber funcionalidades mais avançadas.&#x20;

```bash
## Server
$ echo -e "HTTP/1.1 200\r\n\r\n\r\n<h1>PONG</h1>" > response.html
$ cat response.html | nc -lvN 8080
Listening on 0.0.0.0 8080
```

Agora, ao fazer o request com `curl`:

```bash
$ curl http://localhost:8080/
<h1>PONG</h1>
```

O problema da resposta estática ainda não foi resolvido. E se, ao invés de fazermos o `cat` a partir de um arquivo estático, optarmos por fazer o `cat` a partir de uma estrutura dinâmica?

Tal estrutura pode funcionar como uma fila síncrona, onde o conteúdo da estrutura só é lido quando algum outro processo tiver escrito.&#x20;

Sim, estamos falando de named pipes.

<pre class="language-bash"><code class="lang-bash"><strong>## Server
</strong><strong>$ mkfifo response
</strong>$ cat response | nc -lvN 8080
Listening on 0.0.0.0 8080

## Client
$ curl http://localhost:8080/

## Outra sessão, escrever no named pipe (response)
$ echo -e "HTTP/1.1 200\r\n\r\n\r\n&#x3C;h1>PONG&#x3C;/h1>" > response
</code></pre>

<figure><img src="/files/LTE6eDjDFWECAFwqDIj2" alt=""><figcaption></figcaption></figure>

Isto é incrível!&#x20;

E se, ao invés de escrever no named pipe separadamente, o fizermos depois do tratamento do HTTP request? Sim, como sabemos que o request é enviado para o STDOUT, tudo o que temos de fazer é ler o STDOUT, processá-lo, e depois escrever no named pipe (response) adequadamente, para posteriormente ser enviado no socket como resposta.

```bash
$ mkfifo response
$ cat response | nc -lvN 8080 | handleRequest
```

Onde `handleRequest` será uma função bash que iremos implementar, a qual terá como principal função ler o HTTP request do STDOUT, processá-lo e depois escrever uma resposta dinâmica no response named pipe.

### Processando o HTTP request

Hora de iniciar a implementação do server com a funcionalidade mínima na função `handleRequest`:

```bash
#!/bin/bash

## Cria o named pipe FIFO, que irá representar a resposta HTTP
rm -f response
mkfifo response

function handleRequest() {
  ## Ler o HTTP request até a linha \r\n
  while read line; do
    echo $line
    trline=$(echo $line | tr -d '[\r\n]') ## Remove o \r\n do final da linha

    ## Interrompe o loop de leitura quando a linha for vazia, após a remoção
    ## do \r\n no fluxo anterior
    [ -z "$trline" ] && break
    
    ## Implementar processamento do request a partir desta linha...
  done

  echo -e "qualquer coisa" > response
}

## 1. cria o socket e fica à escuta de conexões na porta 8080
## 2. quando uma conexão é estabelecida, o HTTP request é redirecionado para o input
##    da função `handleRequest`
## 3. a função processa a mensagem do HTTP request e escreve resposta no FIFO
## 4. assim que o FIFO recebe a mensagem, esta é enviada como resposta HTTP no socket
## 5. a conexão com o client é encerrada e o socket é finalizado
cat response | nc -lvN 8080 | handleRequest
```

#### Processar o headline

Ainda dentro da função `handleRequest`, dentro do loop que faz a leitura de cada linha, e utilizando *expressões regulares*, vamos processar o headline, que é basicamente a primeira linha da mensagem HTTP:

```ruby
## Parses the headline
## e.g GET /login HTTP/1.1 -> GET /login
HEADLINE_REGEX='(.*?)\s(.*?)\sHTTP.*?'
[[ "$trline" =~ $HEADLINE_REGEX ]] &&
  REQUEST=$(echo $trline | sed -E "s/$HEADLINE_REGEX/\1 \2/")
```

Okay, mas apenas o headline é necessário? E quanto às outras linhas da mensagem HTTP?&#x20;

Para continuarmos com o fluxo, precisamos antes entender como funciona um login na web, o que será assunto para a próxima seção.


