MSH – Um Mini Servidor HTTP

Aqui começa uma série de tópicos que acompanham o meu empenho em desenhar e construir um mini-servidor HTTP (como o Apache) em C que rode em linux e minix(www.minix3.org).
Irei tentar detalhar um pouco de teoria, aliado à muita prática.
Vale ressaltar que não vou estudar nenhum código-fonte de servidor, então será tudo na base da criatividade e da intuição(sic).

O Projeto vai ser dividido em 3 etapas iterativas:
Estudo, que visa conhecer melhor o conceito de sockets e do protocolo HTTP. Arquitetura, um esqueleto definindo como funcionará o servidor e a Programação em si do servidor. Neste primeiro tópico irei fazer um estudo simples, mostrarei a arquitetura e o ínicio da programação, com um servidor primitivo apenas fornecendo uma resposta HTML simples para o browser , a partir de outros tópicos iremos aprofundar mais e mais.

Porque um mini-servidor http?
O intuito é esforço próprio em aprender melhor sobre sockets e linguagem C, compartilhando o conhecimento adquirido. Não há planejamento em criar um servidor de proporções grandes, apenas um utilizavel.

ATENÇÃO: Muitos conceitos eu irei passar por cima, por tanto se não entender algo, estou deixando muitos links espalhados com explicações, os posts se dedicam em mostrar a implementação do servidor em si.

Objetivo da Fase 1:
Criar um servidor que aceite uma requisição GET vinda de um browser e retorne uma página contendo < h1 >Olá Mundo < /h1 >

Etapa 1 – Estudo

Esta fase somente fala o básico de sockets e http, se você já tem boa leitura sobre, passe adiante.

Sockets

Basicamente falando , sockets são interfaces para comunicação entre duas máquinas. Existem vários tipos de sockets, mas os 2 principais são os Datagram Sockets e os Stream Sockets. Datagram Sockets utilizam-se do protocolo UDP, em que a mensagem é enviada fora de ordem e sem confirmação de recebimento do receptor, e nem mesmo é necessário existir uma conexão estabelecida. Stream Sockets necessitam de uma conexão estabelecida e sempre da confirmação de recebimento do receptor, eles utilizam o protocolo TCP e são utilizados pelo HTTP.
Na imagem a seguir é demonstrada o esquema da comunicação com Stream Socket e suas primitivas. O Socket do Servidor, aguarda ( listen() ) até receber um pedido de conexão do cliente(connect() ) e então o aceita para iniciar a transmissão de dados( accept() ).

Figura 1 – Primitivas de um Stream Socket e sua organização.

HTTP

O protocolo HTTP é baseado em requisições e respostas, o browser realiza uma requisição e o browser devolve os dados pedidos pelo browser. Existem as seguintes requisições para o servidor:

  • OPTIONS
  • GET
  • HEAD
  • POST
  • PUT
  • DELETE
  • TRACE
  • CONNECT

Basicamente falando, a requisição HTTP acontece da seguinte forma, sempre em letra maiúscula , a URI e a versão. Estaremos utilizando a versão 1.1 do HTTP(HTTP/1.1). Ex:

GET /pagina.htm HTTP/1.1

Ao receber esta mensagem, o servidor HTTP irá buscar o arquivo pagina.htm na raiz na qual o servidor está configurado e dar uma resposta ao browser, divida em 3 partes:
Linha de Status
Cabeçalho
Mensagem

A confirmação irá retornar a versão do HTTP do servidor, o código do status(3 dígitos) e uma mensagem, ex:

HTTP/1.1 200 OK

Este código significa que o browser fez uma requisição correta e a página que ele procura existe no servidor.
A seguir, o cabeçalho ajuda o browser a saber mais sobre a mensagem que ele irá receber, ele é montado da seguinte maneira

Date: Mon, 09 Jul 2012 19:07:48 GMT
Server: msh
Content-Type: text/html
Content-Lenght: 1000
Existem mais instruções de cabeçalho, mas nesse momento não é importante sabermos, o importante é que após o cabeçalho é enviada uma linha em branco para delimitar a Linha de Status + Cabeçalho da Mensagem, e então a mensagem contém a página a ser enviada.

Esse é o final da primeira parte dos estudos, para uma leitura mais aprofundada, seguem as referencias que utilizei para escrever este texto:

Sockets:

http://olinux.uol.com.br/artigos/370/1.html

http://www.thegeekstuff.com/2011/12/c-socket-programming/

http://www-usr.inf.ufsm.br/~giovani/sockets/sockets.txt

http://www.linuxhowtos.org/C_C++/socket.htm

HTTP:

http://publib.boulder.ibm.com/infocenter/cicsts/v3r1/index.jsp?topic=%2Fcom.ibm.cics.ts31.doc%2Fdfhtl%2Ftopics%2Fdfhtl_conintro.htm

http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html

Etapa 2 e 3 -Arquitetura e Programação

Nessa primeira parte, a arquitetura do projeto é simples e explicarei aqui resumidamente,  serão 2 sockets, o primeiro socket será o de conexão, a partir que uma conexão seja requisitada, o servidor irá criar um novo socket ( accept() ) que estará trocando as mensagens HTTP.
A principio o servidor apenas irá rodar, esperar uma requisição do browser, responder e terminar.
Enfim , começamos a parte legal que é programar. Para a utilização de sockets em C, são necessários alguns cabeçalhos:
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

As funções são as mesmas primitivas mostradas na figura 1, socket() , bind() e etc. Tudo é muito intuitivo quando se entende como sockets funcionam,Para uma visualização melhor de como funciona a versão inicial já está no github(http://github.com/davidaug/msh) Primeiramente teremos 2 arquivos de código-fonte , main.c , conexao.c e um cabeçalho: conexao.h , espero que desprezem o fato de eu misturar inglês com português, em breve, será tudo em inglês (menos os comentários).
Main.c será o corpo do servidor e conexao.c terá duas funções createServer e closeServer, ambas são auto-explicativas.
Seguindo o passo a passo de funções mostradas na figura 1, teremos um servidor apto a aceitar conexões e trocar mensagens.
Vale algumas ressalvas enquanto você visualiza o código-fonte:

Um socket é um int.

Existe uma uma estrutura chamada sockaddr_in , esta estrutura define todo o socket, e é composto da seguinte forma:

struct sockaddr_in {
short int sin_family; /* família do endereço */
unsigned short int sin_port; /* número da porta */
struct in_addr sin_addr; /* endereço IP */
unsigned char sin_zero[8]; /* complemento da estrutura, utilizar a função bzero */
};

(retirado de: http://www.br-c.org/doku.php?id=sockaddr_in )

Em resumo, a primitiva socket() irá definir “O que” é o socket, e a primitiva bind() define “Aonde está” o socket.
Logo os passos da função createServer serão:
socket() → cria o socket.
Bind() → instancia o socket como um Ipv4 na porta 80
listen() → espera por uma conexão.

Quando uma conexão for estabelecida, a primitiva accept() criará um novo socket de comunicação direta com o browser, a primitiva read() irá ler a requisição do browser(que poderá ser vista no console) e em seguida com a primitiva write, retornará para o browser o “Olá Mundo”, conforme na figura 2.

Figura 2 – msh rodando

Para um melhor entendimento do que aconteceu, recomendo novamente os links:

http://olinux.uol.com.br/artigos/370/1.html

http://www.thegeekstuff.com/2011/12/c-socket-programming/

http://www-usr.inf.ufsm.br/~giovani/sockets/sockets.txt

http://www.linuxhowtos.org/C_C++/socket.htm

O próximo post mostrará o servidor em loop e trabalhar com várias requisições ao mesmo tempo, os commits no repositório podem vir sem um post.

Qualquer dúvida, crítica ou correção basta deixar um comentário ou mandar um e-mail para davidaug23.7@gmail.com .

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>