Requisições HTTP - C

Started by Dark_Side, 16 de November , 2006, 12:12:27 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Dark_Side

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 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" 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
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
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
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 ... 445.0.html

Até a próxima parte ;)

Bye.

whit3_sh4rk

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

whit3_sh4rk

[dica]Este tipo de post, deveria receber algum tipo de destaque,  para não sumir no meio dos outros.. [/dica]

Nem preciso falar do ponto positivo né? :D

[]s

TGA

TGA

@Dark_Side
Parabéns pelo poster cara, realmente uma ótima explicação, informações detalhadas,
sem duvica como o whit3_sh4rk disse, esse tipo de de tópico merece destaque.
Abraços.. aguardo novos tutos.. afinal.. estou  estudando C.. vls
"A IMAGINAÇÃO É MAIS IMPORTANTE QUE O CONHECIMENTO"
__________________________________________________________

insanity

Realmente esse tutorial ficou otimo, parabens aí cara ...

Ðark$pawn

Parabéns, todos os seus posts merecem Ponto Positivo... Acaba de ganhar mais um!!! ;)

Sladrak

Nossa não entendo nada de C,
mas fico sempre impressionado com seu modo de passar informações...
Muito bom todos os seus artigos...

E fico aqui sempre na expectativa de ver seus artigos, sendo postados tbm
na area de Visual Basic...
Possa ate não comentar nos post, mas saiba que vi todos, e me ajudam bastante!

Flw Dark_Side, e se der para atender "meu pedido" ficaria extremamente grato...

Dark_Side

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 o host do servidor):

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.

Anonymous

pq tn entendi em q fechar o socket e abrir outro ?

excelente tutorial  :)

Dark_Side

Quote from: "dudeabot"pq tn entendi em q fechar o socket e abrir outro ?

excelente tutorial  ;)

Bye.