FoxPress – Julio-Agosto 2001

 

Usando Winsock para enviar HTTP

http://www.fpress.com/

 

Por la Redacción de FoxPress                                                   socket.zip

 

 

       Este artículo es una pequeña guía sobre como traerte páginas HTML mediante el control Winsock.OCX

 

       Quizás puedes preguntarte ¿Que interés tiene eso cuando te las puedes bajar con el magnífico IE?

 

       La razón estriba en que puedes manejar las cabeceras de los mensajes enviados para que sea interpretado de una forma u otra por el receptor y también, con muy pocos cambios, puedes llegar a tener un sistema de bajarte archivos en tu aplicación mediante el protocolo http

El proceso de comunicación mediante HTTP, entre dos ordenadores es muy simple. La aplicación cliente establece una conexión TCP/IP a través del puerto 80 de un servidor web, y envía la petición HTTP al servidor. El servidor procesa la petición del cliente, y enviá la respuesta HTTP al cliente y cierra la conexión.

De esta forma, una sesión HTTP consiste de:

·   El cliente establece la conexión

·   El cliente envía la petición HTTP

·   El servidor envía la respuesta http

·   El servidor cierra la conexión

Nota: el estado de una sesión HTTP (cerrar la conexión) es diferente según la versión del protocolo HTTP que se esté usando. Si es la verión 1.0, no necesitas hacer cambios adicionales ya que el servidor cierra automáticamente la conexión después de que ha enviado todos los datos. Pero en la versión 1.1 de HTTP, puedes controlar el estado de la conexión estableciéndolo en la cabecera de la petición. Para forzar el cierre de la conexión se deberá especificar en la cabecera de la forma siguiente:

Connection: close

             Si queremos que la conexión se quede abierta habrá que poner:

Connection: Keep-Alive

El cliente establece la conexión

Este paso lo ejecutamos con esta instrucción:

   THISFORM.oleWinsock.Object.Connect("www.microsoft.com", 80)

Fíjate que ponemos la dirección a la que queremos conectarnos sin el http::// y especificamos también a través de qué puerto vamos a ir.

Como Fox es demasiado rápido es necesario pararlo un poco después de la anterior petición para que al servidor le dé tiempo a responder satisfactoriamente a la petición. Por eso después de esa instrucción es bueno algo como:

             Wait wind ‘Conectando....’ Time 1

 

El cliente envía la petición HTTP

 

Una vez establecida la conexión podemos enviar la petición http al servidor. 

La petición HTTP es un bloque de líneas de código en ASCII. La primera línea contiene la instrucción http, el path relativo del fichero que nos queremos traer y la versión del protocolo. El resto de las líneas son otras cabeceras http con información complementaria. Una mínima petición sería esta:

GET /default.asp HTTP/1.1
Host: www.microsoft.com
Accept: */*
Connection: close

 

Si en vez de la página desfault.asp quisieramos la página de incio por defecto deberíamos poner:

 

GET / HTTP/1.1
Host: www.microsoft.com
Accept: */*
Connection: close

 

Si queremos una cabecera más elaborada podríamos poner:

 

GET / HTTP/1.1
Host: www.microsoft.com
Accept: */*

         User-Agent: miapp

         Pragma: no-cache

         Cache-Control: no-cache

         Connection: close

 

En la documentación del protoclo HTTP puedes ver todas las opciones de las cabeceras. También puedes ver las cabeceras que te envían a ti mediante un esnifador de red como el de http://www.ufasoft.com/sniffer/  que viene en los ficheros de este mes. De esa forma puedes aprender más sobre las cabeceras.

 

En nuestro caso, construiremos una cabecera sencilla y lo podemos hacer con este código:

 

local strHttpRequest

strHttpRequest = "GET / HTTP/1.1" + chr(13) + CHR(10)

strHttpRequest = strHttpRequest + "Host: www.microsoft.com" + ;

CHR(13) + CHR(10)

strHttpRequest = strHttpRequest + "Accept: */*" + CHR(13)+ CHR(10)

strHttpRequest = strHttpRequest + "Connection: close" + CHR(13)+ CHR(10)

strHttpRequest = strHttpRequest + CHR(13)+ CHR(10)

 

En nuestro código observa como la cadena de cada línea acaba con un CHR(13)+ CHR(10) y al final se añade otro CHR(13)+ CHR(10) Lo más importante a tener en cuenta es la línea en blanco que se mete al final debido a que este protocolo distingue la cabecera del cuerpo mediante la existencia de una línea en blanco. Esa línea en blanco le esta diciendo que hemos acabado de entregarle la cabecera.

 

Una vez construida la cabecera la enviamos con:

 

Thisform.oleWinsock.Object.SendData(strHttpRequest)

 

Seguramente te estará preguntando por qué pongo ese ‘Object’ dentro de las sentencias que invocan al Winsock. La razón estriba es que este control tiene métodos y propiedades que se llaman igual (algo que en Fox no se puede hacer) y para distinguir una de otra cuando se le llama necesitamos poner ese Object.

 

 

El servidor envía la respuesta HTTP

 

             Lo que el servidor envía es algo como esto:

 

                 -- HTTP/1.1 200 OK
                |   Server: Microsoft-IIS/5.0
                |   Date: Mon, 10 Jun 2001 10:00:01 GMT

Cabecera HTTP  ----     Content-Type: text/html
                     |   Accept-Ranges: bytes
                     |   Last-Modified: Fri,07 Jun 2001 21:40:36 GMT
                     |   ETag: "d061b8e8d9a0bf1:869"
                      --  Content-Length: 22796
Linea en blanco ---->
                    -- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2                 

                                                  Final//EN">
                    |   <html>
Cuerpo Mensaje --       <head>
                    |   ........
                    --  </html>

 

 

             Aquí hay que comentar varias cosas. Lo primero es conseguir que el servidor nos responda y para eso debe haber un 200 OK que quiere decir que todo ha ido bien. Cualquier otro número sobre todo la saga de los 400 indica que algo ha ido mal.

 

             Lo segundo en lo que tenemos que fijarnos es si la última línea de la cabecera es:

 

                            Content-Length: 22796

 

O por el contrario es:

 

                      Content: chunked

 

             Son dos sistemas que tienen los servidores de enviar el tamaño de la página que van a enviar. En el primer caso viene en ese parámetro y en el segundo al principio de cada uno de los paquetes TCP. De momento no nos detendremos más en este asunto.

 

             Para conseguir meter en una variable de Fox toda la página que nos viene deberemos escribir lo siguiente en el método DataArrival del Winsock:

 

LPARAMETERS bytestotal

 

LOCAL lcData

lcData = SPACE(bytestotal)

WITH THIS.Object

 .GetData(@lcData, 8, bytestotal)

 .Close

ENDWITH

 

? lcHTML = lcData

 

Este es el código que habría que poner aunque con algunos matices. En primer lugar fíjate como este método recibe en un parámetro el tamaño en bytes del paquete actual ( ten en cuenta que un paquete no coincide con la página a recepcionar sino que es un paquete TCP y normalmente la recepción de una página conlleva recepcionar varios paquetes TCP que si me apuras puede que en algún caso lleguen desordenados( el paquete correspondiente al final de la página antes que el correspondiente al del medio) esta es una de las ‘virtudes’ que en este caso se nos puede volver en contra de Internet)

 

Después fíjate como a nuestra variable lcData la hacemos igual al tamaño de los bytes que vienen y conseguimos, por fin, la cadena que nos envían por referencia mediante el @lcData

 

La cadena la recibimos mediante el método GetData del control y luego cerramos la conexión con el Close.

 

Pero aquí hay algunas incongruencias pues este método aunque lo invoquemos una sola vez con la instrucción:

 

Thisform.oleWinsock.Object.SendData(strHttpRequest)

 

Se ejecuta tantas veces como paquetes vamos a recepcionar por tanto no podemos cerrar la conexión después del primer paquete y además si inciamos la variable al principio del método vamos perdiendo la información subsiguiente. ¡Este método hay que escribirlo de otra manera!

 

Ahora lo escribimos de otra manera, pero tal y como está te servirá al principio para ver si te conectas bien y recibes un 200 OK del Servidor.

 

Si el servidor al que nos conectamos no hace un chunked de los datos podríamos escribir el siguiente código:

 

LOCAL lcData

lcData = SPACE(bytestotal)

 

WITH THIS.Object

    .GetData(@lcData, 8, bytestotal) && 8 es vbString

    Thisform.m_strHttpResponse = Thisform.m_strHttpResponse + lcData

     lnInicio = at("Content-Length:",lcData) +15

     lnFinal = at(CHR(13)+CHR(10)+CHR(13)+CHR(10),lcdata)

     lnBytes = substr(lcData,lnInicio, lnfinal -lnInicio)

                    

     IF val(lnBytes) > 0

              Thisform.lnBytes = val(lnBytes)

     ENDIF

     Thisform.lnbytestotal = Thisform.lnBytesTotal + BytesTotal

                  

     IF thisform.lnBytesTotal > INT(Thisform.lnBytes)

             thisform.Edit1.value = Thisform.m_strHttpResponse

            .Close()

   ENDIF                         

ENDWITH

 

Como se ve en este código vamos cargando en Thisform.m_strHttpResponse el contenido de cada uno de los paquetes que se reciben y en Thisform.lnbytestotal  el total de los bytes recibidos. En el momento que el total de los bytes recibidos es superior a lo que nos dice la cabecera "Content-Length:"  en ese momento podemos realizar el cierre con el close. Ten en cuenta que el "Content-Length:"  nos dice el tamaño del cuerpo (sin tener en cuenta la cabecera) por lo que el número de bytes que se reciben supera al dato de esa información.

 

Pero, ¿Y si los datos vienen chunked? Antes de continuar vamos a ver un poco más qué es esto del chunked.

Como se indica en  el RFC2616 http://www.faqs.org/rfcs/rfc2616.html la codificación chunked modifica el cuerpo de un mensaje en orden a transferir el mensaje en una serie de trozos  cada uno con su propio indicador, ....Esto permite enviar contenido e información del tamaño de la información enviada y de esta forma saber que se ha recibido todo el fichero o página.

Esto tiene una consecuencia y es que no sabemos si los mensajes van a ser troceados (ten en cuenta que esto es difernte de paquete: el paquete suele coincidir con un trozo de página más la indicación de comienzo y la indicación de final pero el paquete que lleva la cabecera tiene toda la cabecera y un trozo de página)

De esta forma en nuestro código tendremos que mirar si el mensaje viene chunked o no. Esto varía según los servidores y las horas. Normalmente en las horas de alta demanda un IIS 5.0 sometido a fuertes demandas y peticiones tiende a hacer chunked de todos los envíos. En las horas bajas suele enviarlos enteros.

 

El esquema de los paquetes chunkeados es:

 

<Tamaño del CHUNK>

<CRLF>

<DATOS>

<CRLF>

<Tamaño del CHUNK>

<CRLF>

<DATOS>

...

<Tamaño del Chunk 0 >

El tamaño del Chunk es una cadena hexadecimal que nos indica su tamaño. Cuando el valor es 0 eso nos indica que es el último Chunk. La solución es muy fácil estemos dando vueltas en el bucle hasta que nos llegue el chunk = 0

¿Cómo lo codificaríamos? El mismo código de antes lo preparo para este caso también

 

WITH THIS.Object

   .GetData(@lcData, 8, bytestotal) && 8 is vbString

  

   *- Miramos si es chunked

   IF AT("Transfer-Encoding: chunked",lcData) > 0 Then

           Thisform.llChunked = .T.

           Thisform.m_strHttpResponse = Thisform.m_strHttpResponse + lcData

   ELSE

           IF Thisform.llChunked

                   *- Cojemos el tamaño para ver si es 0

                   IF right(alltrim(lcData),5) = '0' + CHR(13)+CHr(10)+ CHR(13)+CHr(10)

                          Thisform.m_strHttpResponse = Thisform.m_strHttpResponse + lcData

                          Thisform.Edit1.value = Thisform.m_strHttpResponse

                          .close

                          Thisform.Command1.Enabled = .T.

                   ELSE

                          Thisform.m_strHttpResponse = Thisform.m_strHttpResponse + lcDATA

                                  *substr(lcData,at(chr(13) +chr(10)+chr(13) +chr(10),lcData))

                   ENDIF                         

           ELSE

                   *- No está chunked pero hay que comprobar los bytes totales

                   Thisform.m_strHttpResponse = Thisform.m_strHttpResponse + lcData

                    lnInicio = at("Content-Length:",lcData) +15

                    lnFinal = at(CHR(13)+CHR(10)+CHR(13)+CHR(10),lcdata)

                    lnBytes = substr(lcData,lnInicio, lnfinal -lnInicio)

                    

                    IF val(lnBytes) > 0

                           Thisform.lnBytes = val(lnBytes)

                    ENDIF

                    Thisform.lnbytestotal = Thisform.lnBytesTotal + BytesTotal

                  

                    IF thisform.lnBytesTotal > INT(Thisform.lnBytes)

                          thisform.Edit1.value = Thisform.m_strHttpResponse

                          Thisform.Command1.Enabled = .T.

                          .Close()

                                 

                  ENDIF                         

           ENDIF

   ENDIF

 

ENDWITH

 

Como se ve lo primero que hacemos es mirar si es una página chunked o no y para eso preguntamos

 

IF AT("Transfer-Encoding: chunked",lcData) > 0 Then

 

Y como no es el mismo el tratamiento que hay que dar al primer paquete (que viene con la cabecera) del resto de paquetes por eso creamos una variable lógica para indicarnos que estamos con una página chunked pero con el primer paquete o con el resto de paquetes.

 

Todos los paquetes a medida que van llegando los vamos analizando para ver si se encuentra el ‘0’ que nos indique que estamos al final del último paquete. Eso lo conseguimos con el:

 

right(alltrim(lcData),5) = '0' + CHR(13)+CHr(10)+ CHR(13)+CHr(10)

 

Una vez nos llega esa notificación podemos realizar el close

 

Pero este código tiene una limitación es que al ir sumando las cadenas recibidas también nos mete los valores de cada chunk y el indicador CHR(13)+CHr(10)+ CHR(13)+CHr(10) de final de paquete. Es cierto, para ti dejo el quitarlo.

 

Como siempre un buen ejemplo es mejor que varias páginas de explicación y por eso, lo mejor sera ejecutar el código de ejemplo que por defecto se trae la página de inicio de Microsoft y nos lo deja en una pantalla como ésta:

 

 

 

 



 

 

 

 

 

 

Página chunked

 

 

 


Indicador Hexadecimal del Chunk

 

 

 

 

 

 

 

 

 


 

 

FoxPress – Julio-Agosto de 2001

© 2001 FoxPress. All rights reserved