Criando seu próprio servidor HTTP do zero (ou quase) – Parte Final

Os servidores HTTP são parte fundamental da Web como conhecemos, sendo responsáveis por fornecer todo o conteúdo que acessamos através de nossos navegadores. Durante esse tutorial, entenderemos como funciona a comunicação entre o navegador e o servidor e como a informação é entregue ao usuário. Caso não tenha acompanhado os últimos posts, recomendo que leia

Os servidores HTTP são parte fundamental da Web como conhecemos, sendo responsáveis por fornecer todo o conteúdo que acessamos através de nossos navegadores. Durante esse tutorial, entenderemos como funciona a comunicação entre o navegador e o servidor e como a informação é entregue ao usuário.

Caso não tenha acompanhado os últimos posts, recomendo que leia as Partes um, dois e três antes de prosseguir a leitura deste post.

Essa é a última parte do tutorial, mas antes de prosseguir vamos recapitular o que vimos até agora então: Nós conhecemos o protocolo HTTP/1.1, qual o padrão de requisição e resposta, entendemos um pouco de sockets e por fim montamos um mini servidor que recebe requisições HTTP, e devolve a página solicitada.

É claro que nosso servidor não é perfeito, além da função main ter ficado gigante, nosso servidor só responde a uma requisição e para! O ideal é que o servidor permaneça em execução para receber novas requisições e também possa receber várias requisições simultâneas, afinal de contas é para isso que um servidor web serve =D

Organizando o código

Pra ficar simples, vamos separar a requisição da resposta em duas classes diferentes que vou chamar de RequisicaoHTTP e RespostaHTTP

RequisicaoHTTP.java

Veja que simplesmente copiei a parte onde liamos a requisição e imprimíamos na tela, dentro de um método estático lerRequisicao() que retorna um objeto RequisicaoHTTP. Perceba ainda que esse método recebe o InputStream de onde iremos ler a requisição como parâmetro. Além do mais iremos colocar os dados do cabeçalho em um Mapa para facilitar o manuseio desses dados posteriormente caso seja necessário.

Até o momento os únicos dados que utilizávamos da requisição era a primeira linha que contém o caminho do arquivo, a partir de agora vamos usar o Connection (se existir) para saber se manteremos a conexão viva ou não, veja que há uma propriedade tempoLimite que por padrão é 3000 milissegundos (3 segundos), que vamos utilizar para controlar quanto tempo uma conexão deve permanecer ativa. O resto é só você implementar (os métodos getters e setters eu omiti).

RespostaHTTP.java

Veja que para a resposta utilizamos o mesmo conceito, estamos montando o cabeçalho na requisição em um Mapa, criei também outros métodos para auxiliar na geração dos dados pertinentes ao cabeçalho, e sobrescrevi o método toString() para converter o mapa no formato padrão da resposta HTTP, e por fim, o método enviar para enviar a requisição ao servidor.

Servidor.java

Agora o código do nosso servidor está pequeno mas ainda não é o suficiente – continua recebendo uma requisição e respondendo apenas uma vez. Vamos ver mais um conceito:

Threads

As threads, de maneira geral, são segmentos de código que são executados “paralelamente” (ou pelo menos quase) dentro de um mesmo programa. Para exemplificar melhor, pense nisso: imagine que ao abrir um software de grandes proporções, ele tenha que carregar todas as bibliotecas necessárias, mas ao mesmo tempo tem que mostrar ao usuário o progresso do carregamento. A ideia que temos é que esses dois trechos de código são executados paralelamente. Isso é possível graças às threads. Neste exemplo, temos duas threads executando: uma que carrega as bibliotecas e outra que mostra o progresso para o usuário. Dentro de um programa, pode-se ter quantas threads quisermos, e enquanto o programa estiver executando, essas threads podem ser criadas, executadas, terminadas, permitir que novas threads e outros. Quem faz esse controle é a máquina virtual (JVM).

Olha que legal, um servidor recebe várias conexões simultâneas, onde por essas conexões passarão as requisições. Praticamente, todas essas requisições são processadas da mesma maneira, logo, para cada conexão que esse servidor recebe, ele cria uma nova thread, permitindo tratar as requisições de um cliente. Veja só, se temos 5 computadores solicitando uma página, então teremos 5 threads processando essas requisições, e por aí vai.

Agora fica fácil analisar qual segmento do código queremos executar paralelamente. A partir desse segmento iremos montar uma estrutura de Thread, da seguinte maneira:

ThreadConexao.java

A estrutura de uma thread é bem simples: uma classe que implementa a interface Runnable. Essa interface possui um único método a ser implementado, o método run(). Esse método é o nosso segmento de código que queremos que seja executado em paralelo. Veja que nele temos o código que tínhamos na main com apenas algumas modificações para controlar o tempo máximo de conexão (o tempo que a conexão deve se manter ativa).

ThreadPools

Por fim, temos que falar um pouco sobre as Thread Pools, que tem o trabalho de controlar a criação de threads. Claro que podemos criar quantas threads quisermos, mas, às vezes, a situação requer um certo controle, ainda mais quando um servidor web pode receber milhares ou até milhões de requisições por segundo. Por isso, precisamos gerenciar essas threads de maneira eficiente, para que nosso servidor não sobrecarregue. Para isso, o Java tem os Executors, que criam um ambiente de execução de múltiplas threads. Existem diversos tipos de ExecutorService. No nosso caso, iremos utilizar o fixo, que significa limitarmos a criação de threads a um número fixo. Se o número de threads criadas exceder o limite, essas novas threads deverão aguardar até que as outras threads terminem para começar a executar. Com isso, nossa classe Servidor passa a ficar da seguinte forma:

Servidor.java

Veja que agora colocamos a criação de novas threads while(true). Isso impede que nosso servidor pare de executar após a primeira requisição, permitindo que ele aceite múltiplas conexões. Você deve estar a se perguntar – mas um while true não gera um laço infinito? – de certa forma sim, mas para a nossa situação esta é a ideia, já que não queremos que o servidor pare, e que o servidor só finalize quando o usuário enviar o comando CTRL+C no prompt/terminal. De qualquer forma, o método accept() é bloqueado até que receba uma nova conexão, e esse laço sé será executado quando houver uma solicitação, caso contrário, ficará parado num estado de bloqueio =D

Pronto. Agora temos um servidor funcional que aceita conexões múltiplas e responde a muitas requisições.

Considerações finais

Nosso servidor está longe de ser uma versão completa para competir com o Apache e outros servidores HTTP, até por que nosso servidor só envia documentos HTML. Vale lembrar que, quando o navegador recebe um HTML como resposta, ele tem que renderizá-lo, e ao fazer isto, ele encontra tags de arquivos de imagem, áudio, scripts ou estilos, o que gera outras requisições para o servidor, para que ele envie também esses arquivos. O código ainda pode ser melhorado, teríamos que fazer com que o servidor forneça o Content-Type correto para cada tipo de arquivo (o que não é difícil, fica como exercício). Também seria necessário implementar uma camada de segurança (o que hoje em dia é fundamental, pois sem ela nosso servidor está completamente vulnerável a ataques), e por aí vai.

Além do mais, nosso servidor responde ao padrão HTTP/1.1, mas recentemente foi lançado o protocolo HTTP2, que veio para tornar o antigo padrão ainda mais rápido. Embora tenha sofrido alterações internas (o que significa que os servidores HTTP terão que se “adaptar” para seguirem esse novo padrão), o conceito continua o mesmo. Você pode ler um pouco mais sobre HTTP2 nesse post “HTTP/2 – Atualização do protocolo base da internet” e nesse “HTTP2 para Desenvolvedores de Web”.

Espero ter despertado em vocês a vontade de conhecer mais a fundo como as coisas funcionam, para criarem suas próprias contribuições e compartilharem com a galera, afinal, esse é o espirito do Tableless.

Por favor, deixem comentários, se gostaram ou não, erros, dúvidas. O feedback de vocês é importante.

Até a próxima =D

Referências:

Lições sobre socket (em inglês): https://www.oracle.com/technetwork/java/socket-140484.html

Java Tutorial Tudo sobre sockets (em inglês): https://docs.oracle.com/javase/tutorial/networking/sockets/

RFC2616 (em inglês): https://www.w3.org/Protocols/rfc2616/rfc2616.html

Código** Fonte Completo:** https://github.com/thiguetta/MeuServidorHTTP

Versão alternativa que fornece arquivos de imagem, javascript e css também: https://github.com/thiguetta/SimpleHTTPServer

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *