Protocolo HTTP y pequeña implementación
por Maxpowel en dic.01, 2008
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.
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 <!-- $Id: index.html 92365 2007-09-23 14:04:27Z oden $ --> <!-- $HeadURL: svn+ssh://svn.mandriva.com/svn/packages/cooker/apache-conf/current/SOURCES/index.html $ --> <h1>It works!</h1>
Por si a alguien le interesa, es la página de prueba de apache en la que se muestra “It works”
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
<form action="//congdegnu.es/prueba.php”" method="”POST”"> </form>
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
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).
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!
Entradas relacionadas:
- Implementación cliente HTTP 2ª Parte Buenas, debido al chapucero código de mi anterior artículo me...








diciembre 2nd, 2008 on 6:51 pm
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!
diciembre 2nd, 2008 on 7:55 pm
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
diciembre 5th, 2008 on 4:30 pm
Esto es una mentira, tu no escribiste ese código.
diciembre 5th, 2008 on 8:51 pm
ah no? y quién lo ha escrito, tu? Vete a trolear a otro sitio y cuando aportes datos entonces hablas
febrero 19th, 2009 on 11:15 pm
Cierro los comentarios en esta entrada, puesto que llega mucho spam. Si quieres comentar algo en esta entrada manda un email a contacto@congdegnu.es o a congdegnu@gmail.com