Hi,
Estive dando uma olhada em alguns programinas que tinha feito há muito tempo atrás, e percebi que grande parte deles faz uso de requisições HTTP. Entretanto, nunca tinha parado para abordar o tema separadamente. Minha intenção, neste tópico, é abordar o uso de requisições HTTP através de sockets na linguagem C.
» Introdução
» Estrutura básica de uma requisição
» Cabeçalhos
» Fazendo requisições HTTP
» Manipulação de respostas
» Finalizando
------------------------------------
Introdução
------------------------------------
Antes de começarmos, é importante que se tenha uma idéia básica sobre o que é o HTTP. HTTP é a sigla de "Hyper Text Transfer Protocol", o que traduzido seria "Protocolo de Tranferência de Hiper Texto". Todos nós já digitamos no navegador "www.google.com.br" (Pelo menos eu acho, lolz) e este nos exibe a famosa página de busca do google. No entanto, como o navegador procede para obter esta página? E o servidor, como envia essa determinada página para o cliente? Existem parâmetros ou comandos? Ao decorrer do artigo, responderemos às questões colocadas. Ainda falando sobre o Protocolo HTTP, quando acessamos a página do google, o navegador se conecta na porta 80 do servidor - isto mesmo, o protocolo HTTP roda sob a porta 80 - e envia uma requisição HTTP para este. Isso não ocorre apenas com páginas WEB - podemos fazer download de arquivos de som, fotos, músicas, etc.
------------------------------------
Estrutura básica de uma requisição
------------------------------------
Observe o seguinte esquema:
QuoteCliente --------------> Servidor (1)
Cliente <-------------- Servidor (2)
(1) O cliente se conecta na porta 80 do servidor; Envia uma requisição HTTP.
(2) O servidor obtém esta requisição, e dependendo do seu conteúdo, envia uma determinada resposta ao cliente.
Vejamos o que o cliente envia quando acessamos o google:
Cliente:
GET / HTTP/1.1
host: www.google.com.br
Servidor:
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html
Set-Cookie: PREF=ID=134ce5d689cb8eb7:TM=1163583536:LM=
o0O; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; do
Server: GWS/2.1
Transfer-Encoding: chunked
Date: Wed, 15 Nov 2006 09:38:56 GMT
a35
<html><head><meta http-equiv="content-type" content="t-1"><title>Google</title>
[...]
Quer tentar?
Abra o telnet e digite:
telnet www.google.com.br (http://www.google.com.br) 80
Envie:
GET / HTTP/1.1 {ENTER}
host: www.google.com.br {ENTER}
{ENTER}
E veja o que ocorre.
Esta é a estrutura básica de uma requisição HTTP. Cada "comando" equivale a um header (ou cabeçalho).
Poderíamos resumir da seguinte forma:
Quote[CLIENTE]
cabeçalhos
--------------------------
[SERVIDOR]
cabeçalhos de resposta
(código do arquivo requisitado)
--------------------------
------------------------------------
Cabeçalhos
------------------------------------
O que signfica o header: "GET / HTTP/1.1" ?
Este header diz ao servidor que o cliente está requisitando o arquivo principal do diretório - geralmente é o "index.html", "index.html", "index.asp", etc. Em alguns casos é também chamado de "default.asp", "default.php", etc.
O "HTTP/1.1" diz ao servidor que a versão do protocolo usada é a 1.1 - podendo ser tambem 1.0.
O header: "host: www.google.com.br (http://www.google.com.br)" especifica o host (do servidor) onde o arquivo está localizado.
Uma observação importante: após cada header, é necessário enviar uma quebra de linha, que equivale à tecla ENTER ou à constante "\r\n". Ao final da requisição, é necessário incluir mais outra quebra de linha, de tal forma que, a requisição termine em duas quebras de linhas. O motivo? Simplesmente para que o servidor saiba que o header da requisição termina ali.
O servidor antes de nos enviar o código do arquivo, nos envia um header de resposta à requisição feita:
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html
Set-Cookie: PREF=ID=134ce5d689cb8eb7:TM=1163583536:LM=
o0O; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; do
Server: GWS/2.1
Transfer-Encoding: chunked
Date: Wed, 15 Nov 2006 09:38:56 GMT
Geralmente, esse header contém:
1) Código de resposta:
HTTP/1.1 200 OK -> requisição aceita
Obs:: veremos alguns códigos de erro posteriormente
2) Tipo de conteúdo
Content-Type: text/html -> Texto/html
Obs:: veremos outros tipos mais adiante
3) Servidor utilizado:
Server: GWS/2.1
Estes são os principais. É comum encontrar o header "Content-Length" que informa o tamanho do arquivo em bytes.
Como podemos obter outros arquivos do servidor? Muito simples:
GET /lol.jpg HTTP/1.1
host: www.lol.com.br
A requisição acima faria com que o servidor tentasse nos enviar o arquivo "lol.jpg".
Outro exemplo:
GET /lol/lol.mp3 HTTP/1.1
host: www.lol.com.br
Desta vez, o arquivo "lol.mp3" contido dentro do diretório "lol" seria requisitado.
------------------------------------
Fazendo requisições
------------------------------------
Vamos à prática? Abaixo temos um simples exemplo de um programa que faz o download da página inicial do google.
#include <stdio.h>
#include <winsock.h>
int main()
{
WSADATA wsadata;
SOCKET sock;
struct sockaddr_in addr;
struct hostent * host = NULL;
// Incializa winsock
if(WSAStartup(0x101,&wsadata)==-1)
return 1;
// Cria socket
if((sock = socket(AF_INET,SOCK_STREAM,0))==SOCKET_ERROR)
return 1;
// Configura socket
addr.sin_family=AF_INET;
addr.sin_port = htons(80);
// Tenta resolver host
host = gethostbyname("www.google.com.br");
if(host == NULL)
return 1;
// Ajusta IP do host
memcpy(&addr.sin_addr,host->h_addr_list[0],host->h_length);
// Tenta se conectar
if(connect(sock,(struct sockaddr*)&addr,sizeof(addr))==SOCKET_ERROR)
return 1;
// Requisição
char req[] = "GET / HTTP/1.1\r\nhost: www.google.com.br\r\n\r\n";
send(sock,req,strlen(req),0); // Envia requisição
char buffer[0x200]; // Buffer para armazenar dados
int bytes = 1; // Bytes recebidos
while(bytes > 0) // Enquanto estiver recebendo
{
memset(buffer,0x0,0x200); // Limpa o buffer
bytes = recv(sock,buffer,0x200,0); // Recebe dados
printf("%s",buffer); // Mostra-os
}
closesocket(sock); // Fecha socket
WSACleanup(); // Libera winsock
return 0;
}
Existem outros tipos de requisição HTTP além de "GET". O método "POST" por exemplo, que é utilizado para postar dados em formulários. Estes serão abordados em outro artigo.
Já que temos a idéia básica de como o protocolo funciona, veremos alguns headers comuns:
[CLIENTE]
QuoteConnection -> Define como o servidor deve proceder após o envio de dados;
closed -> Faz com que o servidor feche a conexão feche a conexão após o envio do arquivo.
Keep-Alive -> a conexão continuará ativa;
Exemplo:
GET / HTTP/1.1
host: www.lol.com.br (http://www.lol.com.br)
Connection: closed
QuoteRange: bytes= -> Especifica a posição read/write do arquivo de onde este começará a ser transmitido
Exemplos:
+ O exemplo abaixo obtém os bytes de 10 a 100 do arquivo
GET / HTTP/1.1
host: www.lol.com.br (http://www.lol.com.br)
Range: bytes=10-100
+ O exemplo abaixo obtém os bytes de 100 até o fim do arquivo
GET / HTTP/1.1
host: www.lol.com.br (http://www.lol.com.br)
Range: bytes=100-
Obs: nem todos os servidores suportam este header
[SERVIDOR]
QuoteHTTP/X.X YYY ZZZZZZ -> Código de resposta
X.X - versão do protocolo: 1.0 ou 1.1;
YYY - código ID:
200 - requisição OK;
303 - removido temporariamente ou redirecionado;
403 - arquivo negado;
404 - arquivo não encontrado;
416 - conteúdo parcial -> quando há uso do header "Range: bytes="
5xx - problema interno no servidor;
ZZZZZ - descrição da resposta.
QuoteContent-Type -> Define o tipo de conteúdo do arquivo:
text/plain => texto puro
text/html => html
text/xml => xml
image/gif => imagem gif
image/jpeg => imagem jpg
image/png => imagem png
audio/x-wav => som wav
audio/x-aiff => som aiff
video/mpeg => vídeo mpeg
text/richtext => documento RTF
application/pdf => documento PDF
application/msword => Microsoft Word
application/vnd.ms-powerpoint => Microsoft Powerpoint
application/vnd.ms-excel => Microsoft Excel
application/zip => Arquivo .Zip
application/octet-stream => Arquivos binários em geral, geralmente executáveis (.EXE)
QuoteContent-Length -> Tamanho do arquivo em bytes.
------------------------------------
Manipulação de respostas
------------------------------------
Muito bem. Veremos agora algumas informações úteis para trabalharmos com as respostas enviadas pelo servidor:
1) Obtendo apenas headers
Sabemos que uma requisição termina em "\r\n\r\n" (duas quebras de linha). Por lógica, tudo o que vier antes da ocorrência desta constante, corresponde aos headers e o que vem depois, ao código do arquivo requisitado. Partindo deste princípio, poderiámos utilizar a função strtok().
Considerando que a variável "buffer" contém os headers + código do arquivo:
char * headers = strtok(buffer,"\r\n\r\n"); // Separa headers por quebra de linha
while(headers != NULL) // Enquanto houverem headers
{
printf("%s\n",headers); // Mostra-os
headers = strtok(NULL,"\r\n\r\n"); // Continua obtendo headers
}
Exemplo:
Se a variável "buffer" tivesse:
HTTP/1.1 200 OK
Server: LOL
Content-Type: text/html
Content-Length: 10
1234567890
O programa mostraria:
HTTP/1.1 200 OK
Server: LOL
Content-Type: text/html
Content-Length: 10
2) Obtendo apenas conteúdo
Utilizando a mesma lógica, podemos obter o código do arquivo - obter o que vier após "\r\n\r\n".
Podemos utilizar a função strstr():
Considerando que a variável "buffer" contém os headers + código do arquivo:
char * conteudo = strstr(buffer,"\r\n\r\n") + 4; // Obtém a partir de 4 caracteres após "\r\n\r\n"
Exemplo:
Se a variável "buffer" tivesse:
HTTP/1.1 200 OK
Server: LOL
Content-Type: text/html
Content-Length: 10
1234567890
A variável "conteudo" conteria:
1234567890
3) Obtendo conteúdo de um determinado header:
Supondo que temos:
HTTP/1.1 200 OK
Server: LOL
Content-Type: text/html
Content-Length: 10
Como obter apenas o conteúdo do header "Server", por exemplo? Podemos utilizar a função strstr() em conjunto com a função strtok().
Veja:
char * valor = strtok(strstr(buffer,"Server: ")+8,"\r\n");
O programa obteria tudo após "Server:" antes da ocorrência de "\r\n" - que finaliza o header.
------------------------------------
Finalizando
------------------------------------
Para finalizar, irei postar dois links de sources que utilizam requisições HTTP:
http://www.darkers.com.br/smf/index.php ... 537.0.html (http://www.darkers.com.br/smf/index.php/topic,4537.0.html)
http://www.darkers.com.br/smf/index.php ... 445.0.html (http://www.darkers.com.br/smf/index.php/topic,4445.0.html)
Até a próxima parte

Bye.
Cara, simplesmente perfeito...
Você explica muito bem detalhado.. se a pessoa quer realmente aprender ela aprende numa facilidade enorme.. O quantidade de informação que tem aqui já nos poupa de ter que sair pesquisando em outros lugares.. Tem tudo e ainda sources, etc..
Parabéns mesmo.. Ajudou bastante.. Quem tiver dúvidas sobre o "Darkers Team - InfoServer" é só ler esse teu post que a pessoa entende e pode codar uma tool bem mais foda e aprimorada.
[]s
Hi,
Let's go. Iremos continuar com o artigo ;)
Agora sim, podemos ver um outro método de requisição: o método POST.
Muitas vezes, preenchemos e enviamos dados a partir de formulários contidos em páginas. Alguns exemplos são: quando vamos criar um email, quando é necessário registrar-se em algum fórum, consultar um banco de dados, etc. O que se passa por trás disso? Na maioria das vezes, temos uma requisição do tipo POST intermediando dados contidos no formulário e que serão transferidos para o servidor.
Vejamos um exemplo de um formulário:
<form action="login.php" method="post">
Usuário: <input type=text name="user"><br>
Senha: <input type=password name="pass"><br>
<input type=submit value="GOGO">
</form>
A página acima seria um exemplo de um formulário de login. Temos dois campos: "user" e "pass" que correspondem, respectivamente, ao nome de usuário e senha.
Quando enviamos este formulário, o navegador forma uma URL como a seguinte (considere www.lol.com.br (http://www.lol.com.br) o host do servidor):
http://www.lol.com.br/login.php?user=(USUARIO (http://www.lol.com.br/login.php?user=(USUARIO))&pass=(SENHA)
Obviamente, (USUARIO) conterá o que foi digitado no campo "user" e o que foi digitado no campo "pass" será substituído em (SENHA).
Repare o método utilizado:
<form action="login.php" method="post">
Ao enviar os dados para o servidor, o navegador primeiramente conectaria na porta 80 de "www.lol.com.br" e, em seguida, enviaria uma requisição como a abaixo:
POST /login.php HTTP/1.1
host: www.lol.com.br
user=(USUARIO)&pass=(SENHA)
Notamos, visualmente, dois aspectos importantes:
1) Inicialmente, a requisição POST é idêntica à requisição GET;
2) Os dados do formulário foram especificados após os headers e não junto à página "login.php".
Poderíamos resumir a requisição da seguinte forma:
POST /arquivo HTTP/1.1
host: HOST_DO_SERVIDOR
campo1=valor1&campo2=valor2&campo3=valor3...
Sei que é redundante dizer isto, mas é muito importante que se note que os campos são introduzidos após os headers, ou seja, após as duas últimas quebras de linhas ;)
Quando utilizamos o método GET em um formulário, a requisição é feita da seguinte forma:
GET /login.php?user=(USUARIO)&pass=(SENHA) HTTP/1.1
host: www.lol.com.br
Lolz, até agora temos apenas informações sobre o protocolo HTTP, mas o que a linguagem C tem haver com isso até este momento?
Bem, utilizaremos esta linguagem para brincarmos um pouco e colocar em prática algumas informações contidas neste artigo.
Vamos simular uma situação:
Imagine que temos um site com o seguinte host: "www.games.lol". Neste site, nós preenchemos um cadastro com um nome de usuário e uma senha para podermos ter acessos aos jogos que este oferece. Muito bem, analisando o código fonte do site, encontramos o seguinte trecho referente ao formulário:
<form action="registra.php" method="post">
Usuário: <input type=text name="username"><br>
Senha: <input type=password name="pass"><br>
<input type=submit value="Cadastrar">
</form>
Observando-o, sabemos que a página responsável pelo o cadastro é "registra.php" e o método utilizado no formulário é o POST. Com esta última informação, sabemos ainda que os dados enviados NÃO serão mostrados na barra de endereços. Entretanto, como já vimos, a requisição seria feita da seguinte forma:
POST /registra.php HTTP/1.1
host: www.games.lol
username=(USUARIO)&pass=(SENHA)
Lol, e daí? Agora temos que usar um pouco de lógica. Vamos supor que o servidor usa um banco de dados com capacidade para armazenar nomes e senhas de 1000 usuários... Imaginou o que poderíamos fazer? Se não, observe:
Quoteusername=(lol1)&pass=(123)
username=(lol2)&pass=(123)
username=(lol3)&pass=(123)
username=(lol4)&pass=(123)
username=(lol5)&pass=(123)
username=(lol6)&pass=(123)
...
username=(lol1000)&pass=(123)
Se fizéssimos requisições da seguinte forma, o banco de dados seria totalmente preenchido, correto? Pois bem, poderíamos fazer um programinha para automatizar o processo não é mesmo ;)
Até a próxima...
Bye.