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