Code: Select all | Expand
* THTTP por Andrés Romero García
(ARG
) andres.romero@puentelibros.com* Ver ejemplos al
final del programa
* ==================================
#include "FiveWin.ch"#include "romewin.inc" /* Se puede sustituir por:
#define CRLF chr(13)+chr(10)
#define SI .T.
#define NO .F.
El resto del include, no se utiliza en esta clase
*/memvar oWnd
//----------------------------------------------------------------------------//CLASS THTTP
// datos de la clase: DATA oSocket
// socket usado durante la sesion. DATA lSocket
// bandera de haberse creado o no el socket. DATA nSocket
// numero del socket. DATA nPort
// puerto de comunicación. Por defecto, el 80. DATA cIPServer
// IP del servidor. DATA nDelay
// demora intencionada en la recepción de los paquetes. DATA nTiempo
// tiempo empleado en recibir el recurso. DATA lAvisa
// bandera de avisar o no cuando hay timeout. DATA lTimeOut
// error por exceso de tiempo. DATA lTrozos
// respuesta en trozos. DATA nTiempoMax
// tiempo maximo de espera antes de dar error por TimeOut. DATA cCabecera
// cabecera de la respuesta del servidor. DATA cDatos
// datos en la respuesta del servidor. DATA nLargo
// longitud del recurso. DATA lLargo
// definido por longitud. DATA cRecurso
// recurso devuelto en la respuesta del servidor. DATA lMeter
// bandera de utilizar meter o no. DATA cAgente
// texto libre que se manda dentro de la peticion HTTP bajo el apartado "User-Agent".// datos propios del protocolo HTTP. Son respuestas que se reciben del servidor cuando se hace una consulta: DATA h_HTTP
DATA h_RESN
// código de la respuesta en formato númerico, por ejemplo 200, 404, etc. DATA h_RESC
// código de la respuesta en formato texto, por ejemplo, OK, "Página no encontrada", etc. (Lo que importa es el número). DATA h_DATE
DATA h_SERVER
DATA h_LOCATION
DATA h_LAST_MODIFIED
DATA h_ETAG
DATA h_ACCEPT_RANGES
DATA h_CONTENT_LENGTH
DATA h_CONTENT_TYPE
// metodos: METHOD New( cIPServer, nPort
) CONSTRUCTOR
// crea la clase y conecta por medio de un socket. METHOD SendData
( cComando
) // manda una solicitud HTTP al servidor. El texto del comando lo tenemos que componer fuera de la clase METHOD GetData
( cComando
) // manda una solicitud y recibe el recurso HTTP devuelto por el servidor. METHOD End
() // termina la clase y el socket asociado. METHOD get(cDireccion,cRecurso
) // envia una petición por el metodo GET. El comando se crea en el método. Más rápido pero menos flexible. METHOD post
(cDireccion,cRecurso,cPost
) // envia una petición por el metodo POST. El comando se crea en el método. Más rápido pero menos flexible.* Siempre que se pueda, usar post
() mejor que
get()ENDCLASS//----------------------------------------------------------------------------//// la direccion puede ser por IP (84.124.52.140) o por dominio (argcon.net). El puerto por defecto será el 1234. No confundir con el puerto 80METHOD New( cIPServer, nPort, abc
) CLASS THTTP
local cData
DEFAULT nPort
to 1234 // puerto por el que salimos nosotros a internet. NO CONFUNDIR con el 80 que no tiene nada que ver.::
nPort := nPort
::
oSocket := TSocket
():
New( ::
nPort ) // hacer esto ANTES que GetHostByName()::
lSocket := ValType
( ::
oSocket ) ==
"O" .and. ::
oSocket:
nSocket >
0::
nSocket := ::
oSocket:
nSocket::
cIPServer := GetHostByName
( cIPServer
) // para tener la IP desde el nombre, SIEMPRE tiene que crear socket antes.::
lTimeOut := NO
::
nTiempoMax:=
5::
cCabecera :=
''::
cRecurso :=
''::
nDelay :=
0.05::
nTiempo :=
0::
lAvisa := SI
::
lLargo := NO
::
lTrozos := NO
::
lMeter := NO
::
cAgente :=
'ARG-Consulting' // esto es libre, por ejemplo, los navegadores ponen aquí el tipo de navegador MSIE, o CROME, etc.::
h_HTTP :=
''::
h_RESN :=
0 // SOLO este dato es obligatorio. Del resto, algunos servidores mandan lo que quieren.::
h_RESC :=
''::
h_DATE :=
''::
h_SERVER :=
''::
h_LOCATION :=
''::
h_LAST_MODIFIED :=
''::
h_ETAG :=
''::
h_ACCEPT_RANGES :=
''::
h_CONTENT_LENGTH :=
''::
h_CONTENT_TYPE :=
''::
oSocket:
Connect( ::
cIpServer,
80 ) // direccion y puerto por el que escucha el servidor de internet. (p.e. 84.124.52.140, 80 )inkey
(0.5)cData := ::
oSocket:
GetData() // "limpia" el buffer de entrada para empezar de cero.return self//----------------------------------------------------------------------------//METHOD SendData
(cComando
) CLASS THTTP
if !empty
( cComando
) ::
oSocket:
SendData( cComando
)endifreturn NIL//----------------------------------------------------------------------------//METHOD GetData
(cComando
) CLASS THTTP
/*
Si el recurso se recibe en trozos ("chunked"), se van poniendo como elementos de un array.
Cuando llega el ultimo trozo, junta todos los elementos y forma el recurso que será entregado
Si no se pusiera en array y se quiere formar el recurso a medida que se recibe, el ordenador no
tiene tiempo y corta el recurso de mala manera.
// Esta línea se puede poner en cualquier parte del PRG para ayudarnos a hacer una pequeña depuración///////////////////
nCanal := fcreate('.\http'+str0s(++nConCanal,3)+'.txt') ; fwrite(nCanal,cData) ; fclose(nCanal) //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
*/local cData :=
''local nSegundos := seconds
() + ::
nTiempoMaxlocal cCabeMayu
// cabecera recibida convertida a mayçusculas para que sea más fácil buscarlocal nLineas
local nConta :=
0local nPun
local aRecurso :=
{} // array de los "trozos" del recurso recibidolocal nI :=
0local nL :=
0local nLen :=
0local cLinea :=
''local cMayus :=
''local cFinal := CRLF+
'0'+CRLF+CRLF
local nActual :=
0local oMeter
local nConCanal :=
0 // contador para la grabación de paquetes::
nTiempo := seconds
()::
lTimeOut := NO
::
lLargo := NO
::
lTrozos := NO
::
cRecurso :=
''if !empty
(cComando
) ::
oSocket:
SendData( cComando
) // manda el comando de solicitud... inkey
(::
nDelay) // espera x segundos para dar tiempo a no se que ¿?, pero si no lo pongo, no funcionaendifif ::
lMeter @
0.4,
15 METER oMeter
VAR nActual TOTAL
100 of oWnd:
oMsgBar SIZE oWnd:
nWidth-600,
12 BarColor CLR_HRED, CLR_WHITE
oMeter:
ctext :=
'Recibiendo paquetes desde el servidor ...' oMeter:
lPercentage := NO
endifdo while seconds
() < nSegundos
if ::
lMeter if ++nActual >
100 nActual :=
0 endif oMeter:
Set( nActual
) if len
(aRecurso
) =
0 oMeter:
ctext :=
'Esperando recibir paquetes HTTP, desde el servidor ...' else oMeter:
ctext :=
'Recibidos '+allstr
(len
(aRecurso
))+
' paquetes HTTP, desde el servidor de ARG...' endif endif cData := ::
oSocket:
GetData() inkey
(::
nDelay) // espera 0.05 segundos para dar tiempo a no se que ¿?, pero si no lo pongo, no funciona*nCanal := fcreate
('.\http'+str0s
(++nConCanal,
3)+
'.txt') ; fwrite
(nCanal,cData
) ; fclose
(nCanal
) if len
(cData
) >
0 // NO poner "!empty(cData)" porque hay veces que todo el paquete son espacios en blanco nSegundos := seconds
() + ::
nTiempoMax if cData =
'HTTP' // si es el primer trozo... nPun :=
at(CRLF+CRLF,cData
) // ... mira si hay una linea en blanco if nPun >
0 // si hay una linea en blanco, es que tiene una cabecera... ::
cCabecera :=
left(cData,nPun
-1) // ...en cuyo caso, de los datos, separa la cabecera. nLineas := mlcount
(::
cCabecera,
254) // de la cabecera... for nL =
1 to nLineas
// ... analiza todas las lineas de la cabecera cLinea := alltrim
(memoline
(::
cCabecera,
254,nL
)) // toma una linea concreta cMayus := upper
(cLinea
) // pasa a mayusculas por comodidad de programacion do case case cMayus =
'HTTP/1' // si es la primera linea... ::
h_HTTP := cLinea
// linea completa ::
h_RESN := val
(substr(cLinea,
10,
3)) // respuesta numerica ::
h_RESC :=
substr(cLinea,
14) // respuesta en texto case cMayus =
'DATE' ::
h_DATE := cLinea
case cMayus =
'SERVER' ::
h_SERVER := cLinea
case cMayus =
'LOCATION' ::
h_LOCATION:=
substr(cLinea,
11) case cMayus =
'LAST-MODIFIED' ::
h_LAST_MODIFIED := cLinea
case cMayus =
'ETAG' ::
h_ETAG := cLinea
case cMayus =
'ACCEPT-RANGES' ::
h_ACCEPT_RANGES := cLinea
case cMayus =
'CONTENT-LENGTH' ::
h_CONTENT_LENGTH := cLinea
::
nLargo :=
substr(::
h_CONTENT_LENGTH,
16) ::
nLargo := val
(::
nLargo) // la longitud esta en decimal ::
lLargo := SI
case cMayus =
'TRANSFER-ENCODING' if 'CHUNKED' $ cMayus
// la respuesta esta en trozos ::
lTrozos := SI
endif case cMayus =
'CONTENT-TYPE' ::
h_CONTENT_TYPE := cLinea
endcase next ::
cDatos :=
substr(cData,nPun
+4) // del total de datos recibidos, separa el cuerpo if ::
h_RESN <>
200 // si NO es 200, algo raro pasa. Dentro del if, analizamos el que. Se pueden poner mas respuestas if ::
h_RESN =
301 .or. ::
h_RESN =
302 return NO
endif if ::
lAvisa // si hay indicacion de avisar... do case case ::
h_RESN =
404 arginfo
('Página no encontrada',
'Error '+allstr
(::
h_RESN),
'THTTP:228') otherwise arginfo
(::
h_RESC,
'Error '+allstr
(::
h_RESN),
'THTTP:211') endcase endif return NO
endif do case case ::
lTrozos // si viene en trozos ("chunked"), la primera linea es la longitud del recurso (no del paquete) nPun :=
at(CRLF,::
cDatos) // busca el primer CRLF ::
nLargo :=
left(::
cDatos,nPun
-1) // la primera parte es la longitud en hexadecimal ::
nLargo := nHex
(::
nLargo) // lo convertimos a decimal ::
cDatos :=
substr(::
cDatos,nPun
+2) // el resto son los datos propiamente dichos aadd
(aRecurso,::
cDatos) // pone el primer trozo en el array que formara el recurso recibido // vuelve al bucle para seguir recibiendo... case ::
lLargo // si el recurso esta definido por longitud ::
cRecurso :=
left(::
cDatos,::
nLargo) ::
nTiempo := seconds
()-::
nTiempo return SI
otherwise ::
cRecurso := ::
cDatos ::
nTiempo := seconds
()-::
nTiempo return SI
endcase else // si NO hay linea en blanco separando la cabecera del recurso... ::
cCabecera := cData
// todo es cabecera ::
cDatos :=
'' // y por lo tanto, NO hay datos ::
cRecurso :=
'' // NI recurso ::
nTiempo := seconds
()-::
nTiempo return SI
endif else // si NO empieza por HTTP, no es el primer trozo. Es el segundo, tercero, etc. do case case ::
lTrozos // si viene en trozos... if right(cData,
7) = cFinal
// si contiene el final, es que es el ultimo trozo nLen := len
(cData
) aadd
(aRecurso,
left(cData,nLen
-7) ) // tomo los bytes nLen := len
(aRecurso
) for nI =
1 to nLen
::
cRecurso += aRecurso
[nI
] // forma el recurso a partir del array de trozos recibidos next ::
nLargo := len
(::
cRecurso) ::
nTiempo := seconds
()-::
nTiempo if ::
lMeter fin
( @oMeter
) endif return SI
endif aadd
(aRecurso, cData
) // tomo los bytes case ::
lLargo ::
cRecurso += cData
if len
(::
cRecurso) >= ::
nLargo return SI
endif endcase endif endifenddo::
lTimeOut := SI
if ::
lAvisa arginfo
('Exceso de tiempo ('+allstr
(::
nTiempoMax)+
' seg.) en la respuesta a un comando'+CRLF+CRLF+cComando,
'THTTP:298, (solo en ARG)')endifreturn NO
//----------------------------------------------------------------------------//METHOD End
() CLASS THTTP
::
oSocket:
end()::
oSocket :=
NILreturn NIL//----------------------------------------------------------------------------//METHOD get(cDireccion,cRecurso
) CLASS THTTP
local cComando
cComando :=
'GET '+cRecurso+
' HTTP/1.1' +CRLF+;
// SOLO el recurso 'Host: '+cDireccion +CRLF+;
// SOLO la direccion 'Referer: http://'+cDireccion+cRecurso +CRLF+;
// la direccion y el recurso 'Accept: */*' +CRLF+;
'Accept-Language: es' +CRLF+;
'Content-Type: application/x-www-form-urlencoded' +CRLF+;
// es necesario para que cPost sea procesado correctamente 'Content-Length: '+allstr
(len
(cPost
)) +CRLF+;
// len(cPost), es el string SOLO, no incluye el CRLF final 'Connection: close' +CRLF+;
// cierra la conexión una vez recibido el recurso 'Cache-Control: no-cache' +CRLF+;
'User-Agent: '+::
cAgente +CRLF+;
// referencia libre.::
getData(cComando
) // manda el comando y recibe la respuestareturn NILMETHOD post
(cDireccion,cRecurso,cPost
) CLASS THTTP
local cComando
cComando :=
'POST '+cRecurso+
' HTTP/1.1' +CRLF+;
// SOLO el recurso 'Host: '+cDireccion +CRLF+;
// SOLO la direccion 'Referer: http://'+cDireccion+cRecurso +CRLF+;
// la direccion y el recurso 'Accept: */*' +CRLF+;
'Accept-Language: es' +CRLF+;
'Content-Type: application/x-www-form-urlencoded' +CRLF+;
// es necesario para que cPost sea procesado correctamente 'Content-Length: '+allstr
(len
(cPost
)) +CRLF+;
// len(cPost), es el string SOLO, no incluye el CRLF final 'Connection: close' +CRLF+;
// cierra la conexión una vez recibido el recurso 'Cache-Control: no-cache' +CRLF+;
'User-Agent: '+::
cAgente +CRLF+;
// referencia libre. '' +CRLF+;
// linea en blanco separando la cabecera del mensaje cPost +CRLF
::
getData(cComando
) // manda el comando y recibe la respuestareturn NIL************************************************************************************************************************
************************************************************************************************************************
************************************************************************************************************************
/*
/--------------------------------------------------------------------------------------------------------------------\
| LA MEJOR FORMA DE NO PERDER UNA AYUDA ES INCLUIRLA DENTRO DEL PRG QUE USAMOS, AUNQUE ESTO SUPONGA PEOR ENMAQUETADO |
\--------------------------------------------------------------------------------------------------------------------/
El protocolo HTTP se puede utilizar para mantener un "diálogo" entre un cliente (nuestro programa) y un servidor, por
ejemplo Apache. La solicitud y posterior envio de páginas WEB es solo una parte de lo que se puede hacer, aunque es la
parte que más se utiliza. Nosotros con esta clase, pretendemos enlazar el cliente y el servidor para sacar todo el
provecho posible.
Nota:- La función arginfo() es una msginfo() modificada para centrar el dialogo dentro de la ventana padre. Puede ser
sustituida totalmente por msginfo( <parametros> )
El programa esta basado más en la experiencia que en la teoría. Seguro que es muy mejorable técnicamente, pero a mi
me funciona. Solo quiero compartir para mejorar. Espero contestaciones con preguntas e ideas para completar la clase.
EJEMPLOS:
========
Programa para pedir una actualización. Se indican los parámetros fijos y los variables según el cliente. El servidor
Apache-PHP-SQL comprueba el usuario, la clave, el programa, la versión, etc. y responde en consecuencia además de indicar
los datos FTP para la descarga [ evidentemente, los datos están trucados :-) ] :
cDireccion := 'www.la_del_servidor.com'
nPuerto := varget('internet.PUERTOSALI',1234) // varget() es una funcion utilizada en ARG,
cRecurso:= '/php/valikey.php'
// estos parámetros pasan como par variable-contenido, y son leidos como tales en el programa PHP del servidor
cPost := 'version=110118' +;
'&buzon='+cBuzon +;
'&clave='+cPin +;
'&programa='+cGESxxx+cVersion +;
'&diahora='+cDiaHora +;
'&accion=UPDATE' +;
'&clavewin='+cClaveWin +;
'&numepan='+cNumePan +;
''
oWnd:SetMsg('Esperando contestación de ARG. Espere, por favor.' )
oHTTP := Thttp():New( cDireccion, nPuerto ) // prepara los datos, y hace la conexión
if !oHTTP:lSocket // normalmente NO pasará por aquí.
arginfo('No se ha creado el socket en la dirección '+cDireccion)
return NO // variable del preprocesador que indica .F.
endif
oHTTP:nTiempoMax:= varget('internet.TIEMESPE10',10) // segundos que esperara antes de abandonar la conesión. Por defecto 10.
oHTTP:nDelay := varget('internet.INTERPACK',10)/100 // segundos/100 (centésimas de segundo) que espera entre paquetes.
oHTTP:lMeter := SI // bandera para sacar un meter en el pie del diálogo.
oHTTP:lAvisa := NO // bandera de avisar o no al operador del proceso y sus fallos si los hay. Se usa para depuración.
oHTTP:Post(cDireccion,cRecurso,cPost) // manda el comando por POST y recibe la respuesta en oHTTP:cRecurso.
// NO confundir cRecurso (lo que se pide) con oHTTP:cRecurso (lo que se recibe).
if oHTTP:h_RESN = 200 // si todo es correcto...
cRecurso := oHTTP:cRecurso // pasamos el recurso a una variable local.
cRecurso := strtran(cRecurso,'<br>',CRLF) // los cambios de línea (interlínea) vienen como <br> que es propio del protocolo HTTP/HTML.
arglogin(cRecurso) // graba un login con lo que se recibe del servidor, que puede estar bien (200) y NO ser lo que se pide.
memowrit( varget('cDirExe','.')+'\UPDATE.INI', cRecurso) // graba el fichero recibido como UPDATE.INI para que sirva de control al FTP.
lPasa := SI
else
arglogin(oHTTP:h_RESN+CRLF+oHTTP:cRecurso)
lPasa := NO
endif
oHTTP:end()
oWnd:SetMsg(varget('messageWnd',' ') )
if !lPasa
arginfo(cPost,'Respuesta HTTP: '+allstr(oHTTP:h_RESN)+' No se ha recibido el recurso por el socket '+allstr(oHTTP:nSocket ) )
return NO
endif
etc, etc.
Se pueden hacer peticiones GET, pero no merece la pena, es mejor hacerlo siempre POST. El ejemplo anterior quedaría así (OJO, no lo he probado)
cDireccion := 'www.la_del_servidor.com'
nPuerto := varget('internet.PUERTOSALI',1234) // varget() es una funcion utilizada en ARG,
cRecurso:= '/php/valikey.php'
// estos parametros pasan como par variable-contenido, y son leidos como tales en el programa PHP del servidor.
// Aquí cada uno pondría el "dialogo" que quiere mantener con el servidor. Luego hay que programar el servidor para
// leer las variables y su contenido, actuar en consecuencia y devolver el recurso pedido o el resultado programado.
cRecurso:= '?' +; // <-- OJO, esto ha cambiado de POST a GET
'version=110118' +;
'&buzon='+cBuzon +;
'&clave='+cPin +;
'&programa='+cGESxxx+cVersion +;
'&diahora='+cDiaHora +;
'&accion=UPDATE' +;
'&clavewin='+cClaveWin +;
'&numepan='+cNumePan +;
''
oWnd:SetMsg('Esperando contestación de ARG. Espere, por favor.' )
oHTTP := Thttp():New( cDireccion, nPuerto ) // prepara los datos, y hace la conexión
if !oHTTP:lSocket // normalmente NO pasará por aquí.
arginfo('No se ha creado el socket en la dirección '+cDireccion)
return NO // variable del preprocesador que indica .F.
endif
oHTTP:nTiempoMax:= varget('internet.TIEMESPE10',10) // segundos que esperara antes de abandonar la conesión, en segundos. Por defecto 10
oHTTP:nDelay := varget('internet.INTERPACK',10)/100 // segundos/100 (centesimas de segundo) que espera entre paquetes
oHTTP:lMeter := SI // bandera para sacar un meter en el pie del diálogo
oHTTP:lAvisa := NO // bandera de avisar o no al operador del proceso y sus fallos si los hay. Se usa para depuración
oHTTP:get(cDireccion,cRecurso) // <-- OJO // manda el comando por GET y recibe la respuesta en oHTTP:cRecurso
// NO confundir cRecurso (lo que se pide) con oHTTP:cRecurso (lo que se recibe)
if oHTTP:h_RESN = 200 // si todo es correcto...
cRecurso := oHTTP:cRecurso // pasamos el recurso a una variable local
cRecurso := strtran(cRecurso,'<br>',CRLF) // los cambios de linea vienen como <br> que es propio del protocolo HTTP/HTML
arglogin(cRecurso) // graba un login con lo que se recibe del servidor, que puede estar bien y NO ser lo que se pide
memowrit( varget('cDirExe','.')+'\UPDATE.INI', cRecurso) // graba el fichero recibido como UPDATE.INI para que sirva de control al FTP
lPasa := SI
else
arglogin(oHTTP:h_RESN+CRLF+oHTTP:cRecurso)
lPasa := NO
endif
oHTTP:end()
oWnd:SetMsg(varget('messageWnd',' ') )
if !lPasa
arginfo(cPost,'Respuesta HTTP: '+allstr(oHTTP:h_RESN)+' No se ha recibido el recurso por el socket '+allstr(oHTTP:nSocket ) )
return NO
endif
etc, etc.
Este es otro ejemplo, Aqui vemos como se hace una petición desde un programa clipper a un servidor de mensajes SMS para móviles:
El servidor, que no es de ARG, NECESITA que sea GET
cDireccion := '84.124.123.321' // dirección del servidor. Aqui es ficticia
cRecurso := '/pls/sms/sms.CGIEnviaSMS' // NO importa el orden de los siguientes parámetros
cRecurso += '?pusuario=' + cUsuario // fijarse en el signo "?" que hay que poner antes del primer parámetro
cRecurso += '&pclave=' + cClave
cRecurso += '&pdestino=34' + alltrim(aMovil[1])
cRecurso += '&ptexto=' + db2ht(alltrim(cTexto)) // db2ht() es una función que convierte una cadena clipper en una HTTP
cRecurso += '&porigen=' + db2ht(cOrigen)
cRecurso += '&psmsid=' + db2ht(time())
cRecurso += '&pflash=' + 'N'
if lConfirma
cRecurso += '&pacuse=' + cCorreo
endif
oHTTP := Thttp():New( cDireccion ) // prepara los datos, y hace la conexión
oHTTP:nTiempoMax:= varget('internet.TIEMESPE5',5) // segundos que esperara antes de abandonar la conesión
oHTTP:lAvisa := NO
oHTTP:get(cDireccion,cRecurso) // <-- OJO // manda el comando por GET y recibe la respuesta en oHTTP:cRecurso
cRes := oHTTP:h_RESC
nRes := oHTTP:h_RESN
cRecurso:= oHTTP:cRecurso
oHTTP:end()
msgwait('Enviado mensaje '+alltrim(aMovil[1]),'Envio SMS',1)
if cRecurso = 'OK'
arginfo(strtoken(cRecurso,2,'#')+'.' +CRLF+;
'Número: '+alltrim(aMovil[1]) +CRLF+;
'Referencia: '+strtoken(cRecurso,3,'#') +CRLF+;
'Créditos consumidos: '+strtoken(cRecurso,4,'#') +CRLF+;
'Créditos ACTUALES: ' +strtoken(cRecurso,5,'#') )
nSaldo := val(strtoken(cRecurso,5,'#'))
else
arginfo('Respuesta:'+allstr(nRes)+'.-'+cRes+CRLF+;
'Recurso: '+cRecurso)
nSaldo := -1
return NO
endif
Hay que tener en cuenta el conjunto de caracteres, que en HTTP NO se usa el juego de windows, pero eso es otro tema.
*/
Los que empeceís en programación, no ver mi forma de programar como un ejemplo. Los que sabeís mas que yo (casi todos) ser comprensivos y perdonarme.
NOTA IMPORTANTE: Para los que quieran saber más sobre el tema del protocolo HTTP buscar en internet el documento RFC 2616. Es un documento de casi 200 páginas en inglés perfectamente ilegible