Protocolo HTTP y pequeña implementación

Quizá te hayas preguntado alguna vez cómo hacen los navegadores (firefox, konqueror…) para entrar en un página o incluso “qué” es una página web. Sabemos que poniendo en la barra “google.es” nos carga y así de fácil pero ni mucho menos, es bastante complejo.

Todos los navegadores son capaces de cargar las paginas (aunque luego es otra historia el que se visualicen igual) así algo común habrá (estándares)

En la informática, las “capas” juegan un papel muy importante. Una capa es por así decirlo un intermediario, recibe algo de la capa anterior lo procesa y se lo manda a la capa siguiente. El ejemplo más fácilmente visible lo vemos en la capa física donde se reciben impulsos eléctricos y una lo convierte en señales lógicas. Hasta que nosotros vemos la página por pantalla toda esa información ha pasado por unas cuantas capas de red (ver modelo OSI) y el protocolo HTTP está en la cumbre y es, por así decirlo, un protocolo muy “procesado”, tanto que una persona puede comunicarse directamente con un servidor web escribiendo mediante su teclado los comandos.

Verás que gracias este sistema de capas el trabajo que nos toca hacer será muy sencillo pero a su vez muy potente debido a su modularidad.

Todo este tema de las capas es muy extenso, demasiado como para tratarlo en un solo artículo así que me centraré únicamente en HTTP.

Comienzo con mi historia. Hace un tiempo me interesé por la creación de bots para la web. El primer programa útil de este tipo que desarrollé fue un script en perl que subía los torrents de nuestra página a mininova (una página en la que también estaba dicren, donde no meta este las narices… jeje). La ventaja de perl es que es muy fácil de utilizar y para tareas sencillas es perfecto pero cuando se complica la cosa enseguida quedan muchas líneas de código y perl es bien conocido por su poca legibilidad así que me interesé por hacerlo en c++

No encontré ninguna biblioteca específica para estos temas (con curl se puede a hacer pero yo no necesitaba algo tan complejo) así que decidí hacer mi propia biblioteca. Para ello tuve que documentarme acerca del funcionamiento de HTTP, y mi objetivo es resumir y explicar todo aquello que a mi me llevo horas de búsqueda y pruebas.

El protocolo HTTP trabaja sobre TCP/IP (como habrás visto en el enlace de la wiki jeje). TCP/IP fue implementado originalmente en UNIX y, en mi opinión, obtuvo como herencia que toda la comunicación que se realiza a través de dicho protocolo se hace con palabras comprensibles por las personas y no con chorros de bits ilegibles por una persona.

Hoy en día no se puede hablar de HTTP sin mencionar DNS. Los nombres de dominio se inventaron porque para una persona es mucho más sencillo aprenderse una palabra, por ejemplo www.congdegnu.es, que una dirección IP como puede ser 63.134.42.112. Si sólo conociéramos un par de páginas pues quizá podríamos recordar fácilmente esos números pero con el mogollón de páginas diferentes que visitamos pues tendríamos la cabeza llena de numerajos y todos ellos mezclados entre sí. Otro motivo de es que facilitan mucho la administración ya que puedes cambiar la dirección IP las veces que quieras (algo común) sin necesidad de que los usuarios tengan que aprenderse otro número y que te odien todavía más (si cabe). Bien, el DNS es “sencillo”, un cliente pregunta a un servidor DNS “oye dime la ip de congdegnu.es porfa” y el servidor dns le responde “pues es 132.12.53.211” y el cliente establece la conexión con esa ip. Quédate con este concepto porque más tarde lo necesitaremos.

Resolviendo un dominio a través del DNS

Resolviendo un dominio a través del DNS

La clave de la comunicación HTTP está en las cabeceras. Ahí se indica toda la información (cookies, tipo de consulta, tamaño del cuerpo y cualquier información que quiera ser transmitida) y ahí es donde está la conversación que mantiene el servidor con el cliente. Lo que nosotros vemos es el cuerpo, que consiste simplemente en texto plano, el código HTML que luego el navegador tendrá que interpretar. Para el servidor ese código es simplemente mercancía que tiene que entregar al cliente, nada más.

La sintaxis de la cabecera es la siguiente:
Atributo: contenidorn

r y n son retroceso (ir al principio de la línea) y salto de línea respectivamente, sirven para indicar que una línea a terminado.

Ej: Content-Size: 18530rn

Si vemos eso en la cabecera quiere decir que el cuerpo (el html) ocupa 18530 bytes.

Para mí, las cookies son un caso especial de cabecera y su estructura es la siguiente:
Set-Cookie: Elemento1=valor1; Elemento2=valor2;rn

Digo que es especial porque por una parte guarda la estructura “Atributo: contenido” pero a su vez el contenido también guarda varios elementos del tipo “Elemento1=valor1;”

un ejemplo podria ser este:
Set-Cookie: usuario=maxpowel; pais=españa; baneado=1;rn

Observamos que dentro en esta línea de la cabecera se dice que hay una cookie (Set-Cookie) y su contenido esta formado por el nombre de usuario, el país y si está baneado (que habrá hecho para que le baneen jaja)

Y termina con una línea en blanco, como todas.

Un ejemplo de página web sencilla sería esto:

HTTP/1.1 200 OK
Date: Mon, 1 Dec 2008 17:27:40 GMT
Server: Apache/2.2.9 (Mandriva Linux/PREFORK-12mdv2009.0)
Last-Modified: Sat, 26 Jul 2008 14:12:41 GMT
ETag: "4cdf2-d7-452ede21b2840"
Accept-Ranges: bytes
Content-Length: 215
Content-Type: text/html                                      

 
 

It works!

Por si a alguien le interesa, es la página de prueba de apache en la que se muestra “It works”

Funciona!

Funciona!

Esta es la respuesta del servidor pero… ¿Cómo hacemos para preguntarle?

Tenemos dos formas de “preguntar” al servidor. Los métodos GET y POST

El ejemplo mas sencillo de GET podría ser este:
GET /prueba.php HTTP/1.1

Host: localhost

User-Agent: Mozilla/4.0

En la primera línea indicamos el método (GET) la ruta (/prueba.php) y la versión del protocolo (HTTP/1.1)

después el host al que atacamos y por último informamos del agente que usamos. Como todo esto se puede construir manualmente, el User-Agent puede ser el que queramos que al servidor no le queda más remedio que creérselo. Podemos inventarnos más campos si queremos pero seguramente el servidor los ignorará aunque siempre puede haber bugs en el software del servidor web y jugando con las cabeceras podemos explotarlos, pero eso ahora no viene al caso jeje.

Con cabeceras de este tipo podemos comprender cómo una misma IP puede tener todos muchos dominios. Como vimos antes, la conexión se realiza contra una dirección IP y el servidor sabe a qué dominio hacemos referencia gracias al elemento “Host” de la cabecera

Si por ejemplo la página completa es del tipo
http://congdegnu.es/prueba.php?seccion=portada&id=1 la cabecera sería una cosa así:

http://congdegnu.es/prueba.php?seccion=portada&id=1 la cabecera sería una cosa así:
GET / prueba.php?seccion=portada&id=1 HTTP/1.1
Host: congdegnu.es
User-Agent: Mozilla/4.0

Sencillo, ¿A que sí?

Y el otro método sería algo así.

Ejemplo de POST:

POST /prueba.php HTTP/1.1

Host: congdegnu.es

User-Agent: Mozilla/4.0

Content-Length: 27

Content-Type: application/x-www-form-urlencoded

campo1=valor1&campo2=valor2

Este formulario correspondería a este en HTML

Y por supuesto al igual que con GET podemos introducir urls complejas.

Se me olvidaba! Indicamos al servidor que hemos terminado enviando una línea vacía

rnrn

Ahora la pregunta es… ¿Y que hago yo con todo esto? Pues vamos a hacer un programita para poner en práctica nuestros conocimientos. En mi caso he usado C++ pero es fácilmente exportable a cualquier otro lenguaje, solo son necesarios los sockets, manejo de strings y poco más.

¿Y qué es un socket? Pues simplemente la utilidad que nos permite establecer una conexión remota.

Explicación del programa:

Vamos a crear un socket que nos conecte por el puerto 80 al servidor web. Después le enviaremos nuestras cabeceras y esperaremos a la respuesta. Así de fácil.

Explicaré las líneas mas críticas y el resto lo adjuntare como archivo porque son bastantes líneas como para ponerlo aquí a pelo. Subí mis bibliotecas a source forge (http://openbrowserbot.sourceforge.net/) por si a alguien le interesa, pero está en fase de desarrollo así que no esperéis maravillas jeje.

Aquí los haremos todo manual.
El primer problema con el que nos encontramos es que un socket se conecta con una dirección IP pero normalmente usamos dominios para acceder a las páginas, que problemón! Por suerte podemos resolver la dirección ip de un dominio:

//Incluimos estas bibliotecas

#include netinet/in.h
#include netdb.h
#include arpa/inet.h

const hostent* host_info = 0 ;  //Estructura donde se almacenara información del host
in_addr* address;  //Donde se guarda la direccion ip
struct sockaddr_in serv_addr;  //Dirección ip entendible por un socket
serv_addr.sin_family = AF_INET; // Tipo de familia
serv_addr.sin_port = htons(80);  // El puerto

host_info = gethostbyname(“www.google.es”) ;  //Obtenemos la info del host
address = (in_addr*)host_info->h_addr_list[0] ; //Extraemos la IP
inet_aton(inet_ntoa( *address ), &(serv_addr.sin_addr));  //La convertimos
memset(&(serv_addr.sin_zero), '', 8);

Nota: poner los símbolos en las bibliotecas (<netinet/in.h> por ejemplo) que me he dado cuenta de que desaparecen

Ahora tenemos la dirección IP del dominio en serv_addr

Conectamos de esta manera

int formSocket; //Creamos la variable

formSocket = socket(AF_INET, SOCK_STREAM, 0); //Creamos el socket

connect(formSocket,(const struct sockaddr*)&serv_addr,sizeof(serv_addr)) //Conectamos

y ahora simplemente escribimos por el socket

write(formSocket,”TEXTO”,tamaño del texto);

Por ejemplo:

string texto=”GET / HTTP/1.1rnHost: google.esrnUser-Agent: Mozilla/4.0rnrn”;

write(formSocket,texto,texto.length());

Y ahora simplemente leemos

char buffer[2];

string respuesta;

while(read(formSocket,buffer,1)) //Leemos 1 byte, es recomendable usar buffers mas grandes pero como esto es un ejemplo me vale

{

      respuesta.insert(response.length(),buffer); //Metemos la letra en el string

      bzero(buffer,2); //Borramos el buffer (que tiene de tamaño 2 bytes)

}

cout respuesta;

Nota: es cout << respuesta pero las << desaparecen

Parece que funciona

Parece que funciona

Y básicamente es esto :)

Pongo para descargar un ejemplo. El programa tarda en mostrar la pagina unos segundos porque espera a que el servidor finalice la conexión. Una idea es extraer de la cabecera el tamaño del cuerpo y dejar de leer cuando se llegue a ese tamaño pero eso ya te lo dejo a ti (aunque en el ejemplo pongo como hacerlo jeje).

Descargar fuentes

Soy consciente de que si no te manejas bien con C de poco te va a servir este manual pero mi intención en este manual no es enseñar C, sino mostrar como realizar una conexión HTTP ahorrándote todo el curro que lleva documentarse jeje.

Venga, hasta otra!

You may also like...

6 Responses

  1. deckerix dice:

    vaya peazo manual te has montado hoy!! :-)
    La verdad que has comentado mil cosas.. y de cada una podríamos hablar largo y tendido mil horas.
    Está bien para que la gente se dé una idea de como funciona todo.. y que no es magia borras!

  2. Maxpowel dice:

    Gracias 😛
    Si, este tema es muy largo y complejo y me he decantado más por la parte práctica que es lo que más cuesta encontrar por ahí jeje.

    Así que si hay alguna duda o algo, aquí estoy 😀

  3. mentira dice:

    Esto es una mentira, tu no escribiste ese código.

  4. Maxpowel dice:

    ah no? y quién lo ha escrito, tu? Vete a trolear a otro sitio y cuando aportes datos entonces hablas

  1. 6 enero, 2009

    […] decidí aprovechar ese código y realicé un híbrido C/C++ (la versión del otro artículo) pero el código que quedó era muy feo y de muchas líneas así que dije “ala, a empezar […]